// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Included first as it collides with the X11 headers.
#include "gtest/gtest.h"

#include "flutter/shell/platform/linux/fl_binary_messenger_private.h"
#include "flutter/shell/platform/linux/fl_engine_private.h"
#include "flutter/shell/platform/linux/fl_method_codec_private.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_basic_message_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_method_channel.h"
#include "flutter/shell/platform/linux/public/flutter_linux/fl_standard_method_codec.h"
#include "flutter/shell/platform/linux/testing/fl_test.h"
#include "flutter/shell/platform/linux/testing/mock_renderer.h"

// Called when the method call response is received in the InvokeMethod
// test.
static void method_response_cb(GObject* object,
                               GAsyncResult* result,
                               gpointer user_data) {
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
      FL_METHOD_CHANNEL(object), result, &error);
  EXPECT_NE(response, nullptr);
  EXPECT_EQ(error, nullptr);

  FlValue* r = fl_method_response_get_result(response, &error);
  EXPECT_NE(r, nullptr);
  EXPECT_EQ(error, nullptr);

  EXPECT_EQ(fl_value_get_type(r), FL_VALUE_TYPE_STRING);
  EXPECT_STREQ(fl_value_get_string(r), "Hello World!");

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks if invoking a method returns a value.
TEST(FlMethodChannelTest, InvokeMethod) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));

  g_autoptr(FlValue) args = fl_value_new_string("Hello World!");
  fl_method_channel_invoke_method(channel, "Echo", args, nullptr,
                                  method_response_cb, loop);

  // Blocks here until method_response_cb is called.
  g_main_loop_run(loop);
}

// Called when the method call response is received in the
// InvokeMethodNullptrArgsMessage test.
static void nullptr_args_response_cb(GObject* object,
                                     GAsyncResult* result,
                                     gpointer user_data) {
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
      FL_METHOD_CHANNEL(object), result, &error);
  EXPECT_NE(response, nullptr);
  EXPECT_EQ(error, nullptr);

  FlValue* r = fl_method_response_get_result(response, &error);
  EXPECT_NE(r, nullptr);
  EXPECT_EQ(error, nullptr);
  EXPECT_EQ(fl_value_get_type(r), FL_VALUE_TYPE_NULL);

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks if a method can be invoked with nullptr for arguments.
TEST(FlMethodChannelTest, InvokeMethodNullptrArgsMessage) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));

  fl_method_channel_invoke_method(channel, "Echo", nullptr, nullptr,
                                  nullptr_args_response_cb, loop);

  // Blocks here until nullptr_args_response_cb is called.
  g_main_loop_run(loop);
}

// Called when the method call response is received in the
// InvokeMethodError test.
static void error_response_cb(GObject* object,
                              GAsyncResult* result,
                              gpointer user_data) {
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
      FL_METHOD_CHANNEL(object), result, &error);
  EXPECT_NE(response, nullptr);
  EXPECT_EQ(error, nullptr);

  EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response));
  EXPECT_STREQ(
      fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)),
      "CODE");
  EXPECT_STREQ(
      fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)),
      "MESSAGE");
  FlValue* details =
      fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(response));
  EXPECT_NE(details, nullptr);
  EXPECT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_STRING);
  EXPECT_STREQ(fl_value_get_string(details), "DETAILS");

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks if an error response from a method call is handled.
TEST(FlMethodChannelTest, InvokeMethodError) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));

  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string("CODE"));
  fl_value_append_take(args, fl_value_new_string("MESSAGE"));
  fl_value_append_take(args, fl_value_new_string("DETAILS"));
  fl_method_channel_invoke_method(channel, "Error", args, nullptr,
                                  error_response_cb, loop);

  // Blocks here until error_response_cb is called.
  g_main_loop_run(loop);
}

// Called when the method call response is received in the
// InvokeMethodNotImplemented test.
static void not_implemented_response_cb(GObject* object,
                                        GAsyncResult* result,
                                        gpointer user_data) {
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
      FL_METHOD_CHANNEL(object), result, &error);
  EXPECT_NE(response, nullptr);
  EXPECT_EQ(error, nullptr);

  EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response));

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks if a not implemeneted response from a method call is handled.
TEST(FlMethodChannelTest, InvokeMethodNotImplemented) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));

  fl_method_channel_invoke_method(channel, "NotImplemented", nullptr, nullptr,
                                  not_implemented_response_cb, loop);

  // Blocks here until not_implemented_response_cb is called.
  g_main_loop_run(loop);
}

// Called when the method call response is received in the
// InvokeMethodFailure test.
static void failure_response_cb(GObject* object,
                                GAsyncResult* result,
                                gpointer user_data) {
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response = fl_method_channel_invoke_method_finish(
      FL_METHOD_CHANNEL(object), result, &error);
  EXPECT_EQ(response, nullptr);
  EXPECT_NE(error, nullptr);

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks if an engine failure calling a method call is handled.
TEST(FlMethodChannelTest, InvokeMethodFailure) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel =
      fl_method_channel_new(messenger, "test/failure", FL_METHOD_CODEC(codec));

  fl_method_channel_invoke_method(channel, "Echo", nullptr, nullptr,
                                  failure_response_cb, loop);

  // Blocks here until failure_response_cb is called.
  g_main_loop_run(loop);
}

// Called when a method call is received from the engine in the
// ReceiveMethodCallRespondSuccess test.
static void method_call_success_cb(FlMethodChannel* channel,
                                   FlMethodCall* method_call,
                                   gpointer user_data) {
  EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo");
  EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)),
            FL_VALUE_TYPE_STRING);
  EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)),
               "Marco!");

  g_autoptr(FlValue) result = fl_value_new_string("Polo!");
  g_autoptr(GError) error = nullptr;
  EXPECT_TRUE(fl_method_call_respond_success(method_call, result, &error));
  EXPECT_EQ(error, nullptr);
}

// Called when a the test engine notifies us what response we sent in the
// ReceiveMethodCallRespondSuccess test.
static void method_call_success_response_cb(
    FlBinaryMessenger* messenger,
    const gchar* channel,
    GBytes* message,
    FlBinaryMessengerResponseHandle* response_handle,
    gpointer user_data) {
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response =
      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error);
  EXPECT_NE(response, nullptr);
  EXPECT_EQ(error, nullptr);

  EXPECT_TRUE(FL_IS_METHOD_SUCCESS_RESPONSE(response));
  FlValue* result = fl_method_success_response_get_result(
      FL_METHOD_SUCCESS_RESPONSE(response));
  EXPECT_EQ(fl_value_get_type(result), FL_VALUE_TYPE_STRING);
  EXPECT_STREQ(fl_value_get_string(result), "Polo!");

  fl_binary_messenger_send_response(messenger, response_handle, nullptr,
                                    nullptr);

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks the shell able to receive and respond to method calls from the engine.
TEST(FlMethodChannelTest, ReceiveMethodCallRespondSuccess) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel, method_call_success_cb,
                                            nullptr, nullptr);

  // Listen for response from the engine.
  fl_binary_messenger_set_message_handler_on_channel(
      messenger, "test/responses", method_call_success_response_cb, loop,
      nullptr);

  // Trigger the engine to make a method call.
  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
  fl_value_append_take(args, fl_value_new_string("Foo"));
  fl_value_append_take(args, fl_value_new_string("Marco!"));
  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
                                  nullptr, loop);

  // Blocks here until method_call_success_response_cb is called.
  g_main_loop_run(loop);
}

// Called when a method call is received from the engine in the
// ReceiveMethodCallRespondError test.
static void method_call_error_cb(FlMethodChannel* channel,
                                 FlMethodCall* method_call,
                                 gpointer user_data) {
  EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo");
  EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)),
            FL_VALUE_TYPE_STRING);
  EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)),
               "Marco!");

  g_autoptr(FlValue) details = fl_value_new_string("DETAILS");
  g_autoptr(GError) error = nullptr;
  EXPECT_TRUE(fl_method_call_respond_error(method_call, "CODE", "MESSAGE",
                                           details, &error));
  EXPECT_EQ(error, nullptr);
}

// Called when a the test engine notifies us what response we sent in the
// ReceiveMethodCallRespondError test.
static void method_call_error_response_cb(
    FlBinaryMessenger* messenger,
    const gchar* channel,
    GBytes* message,
    FlBinaryMessengerResponseHandle* response_handle,
    gpointer user_data) {
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response =
      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error);
  EXPECT_NE(response, nullptr);
  EXPECT_EQ(error, nullptr);

  EXPECT_TRUE(FL_IS_METHOD_ERROR_RESPONSE(response));
  EXPECT_STREQ(
      fl_method_error_response_get_code(FL_METHOD_ERROR_RESPONSE(response)),
      "CODE");
  EXPECT_STREQ(
      fl_method_error_response_get_message(FL_METHOD_ERROR_RESPONSE(response)),
      "MESSAGE");
  FlValue* details =
      fl_method_error_response_get_details(FL_METHOD_ERROR_RESPONSE(response));
  EXPECT_EQ(fl_value_get_type(details), FL_VALUE_TYPE_STRING);
  EXPECT_STREQ(fl_value_get_string(details), "DETAILS");

  fl_binary_messenger_send_response(messenger, response_handle, nullptr,
                                    nullptr);

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks the shell able to receive and respond to method calls from the engine.
TEST(FlMethodChannelTest, ReceiveMethodCallRespondError) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel, method_call_error_cb,
                                            nullptr, nullptr);

  // Listen for response from the engine.
  fl_binary_messenger_set_message_handler_on_channel(
      messenger, "test/responses", method_call_error_response_cb, loop,
      nullptr);

  // Trigger the engine to make a method call.
  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
  fl_value_append_take(args, fl_value_new_string("Foo"));
  fl_value_append_take(args, fl_value_new_string("Marco!"));
  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
                                  nullptr, loop);

  // Blocks here until method_call_error_response_cb is called.
  g_main_loop_run(loop);
}

// Called when a method call is received from the engine in the
// ReceiveMethodCallRespondNotImplemented test.
static void method_call_not_implemented_cb(FlMethodChannel* channel,
                                           FlMethodCall* method_call,
                                           gpointer user_data) {
  EXPECT_STREQ(fl_method_call_get_name(method_call), "Foo");
  EXPECT_EQ(fl_value_get_type(fl_method_call_get_args(method_call)),
            FL_VALUE_TYPE_STRING);
  EXPECT_STREQ(fl_value_get_string(fl_method_call_get_args(method_call)),
               "Marco!");

  g_autoptr(GError) error = nullptr;
  EXPECT_TRUE(fl_method_call_respond_not_implemented(method_call, &error));
  EXPECT_EQ(error, nullptr);
}

// Called when a the test engine notifies us what response we sent in the
// ReceiveMethodCallRespondNotImplemented test.
static void method_call_not_implemented_response_cb(
    FlBinaryMessenger* messenger,
    const gchar* channel,
    GBytes* message,
    FlBinaryMessengerResponseHandle* response_handle,
    gpointer user_data) {
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(GError) error = nullptr;
  g_autoptr(FlMethodResponse) response =
      fl_method_codec_decode_response(FL_METHOD_CODEC(codec), message, &error);
  EXPECT_NE(response, nullptr);
  EXPECT_EQ(error, nullptr);

  EXPECT_TRUE(FL_IS_METHOD_NOT_IMPLEMENTED_RESPONSE(response));

  fl_binary_messenger_send_response(messenger, response_handle, nullptr,
                                    nullptr);

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks the shell able to receive and respond to method calls from the engine.
TEST(FlMethodChannelTest, ReceiveMethodCallRespondNotImplemented) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(
      channel, method_call_not_implemented_cb, nullptr, nullptr);

  // Listen for response from the engine.
  fl_binary_messenger_set_message_handler_on_channel(
      messenger, "test/responses", method_call_not_implemented_response_cb,
      loop, nullptr);

  // Trigger the engine to make a method call.
  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
  fl_value_append_take(args, fl_value_new_string("Foo"));
  fl_value_append_take(args, fl_value_new_string("Marco!"));
  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
                                  nullptr, loop);

  // Blocks here until method_call_not_implemented_response_cb is called.
  g_main_loop_run(loop);
}

// A test method codec that always generates errors on responses.
G_DECLARE_FINAL_TYPE(TestMethodCodec,
                     test_method_codec,
                     TEST,
                     METHOD_CODEC,
                     FlMethodCodec)

struct _TestMethodCodec {
  FlMethodCodec parent_instance;

  FlStandardMethodCodec* wrapped_codec;
};

G_DEFINE_TYPE(TestMethodCodec, test_method_codec, fl_method_codec_get_type())

static void test_method_codec_dispose(GObject* object) {
  TestMethodCodec* self = TEST_METHOD_CODEC(object);

  g_clear_object(&self->wrapped_codec);

  G_OBJECT_CLASS(test_method_codec_parent_class)->dispose(object);
}

// Implements FlMethodCodec::encode_method_call.
static GBytes* test_method_codec_encode_method_call(FlMethodCodec* codec,
                                                    const gchar* name,
                                                    FlValue* args,
                                                    GError** error) {
  EXPECT_TRUE(TEST_IS_METHOD_CODEC(codec));
  TestMethodCodec* self = TEST_METHOD_CODEC(codec);
  return fl_method_codec_encode_method_call(
      FL_METHOD_CODEC(self->wrapped_codec), name, args, error);
}

// Implements FlMethodCodec::decode_method_call.
static gboolean test_method_codec_decode_method_call(FlMethodCodec* codec,
                                                     GBytes* message,
                                                     gchar** name,
                                                     FlValue** args,
                                                     GError** error) {
  EXPECT_TRUE(TEST_IS_METHOD_CODEC(codec));
  TestMethodCodec* self = TEST_METHOD_CODEC(codec);
  return fl_method_codec_decode_method_call(
      FL_METHOD_CODEC(self->wrapped_codec), message, name, args, error);
}

// Implements FlMethodCodec::encode_success_envelope.
static GBytes* test_method_codec_encode_success_envelope(FlMethodCodec* codec,
                                                         FlValue* result,
                                                         GError** error) {
  g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
              "Unsupported type");
  return nullptr;
}

// Implements FlMethodCodec::encode_error_envelope.
static GBytes* test_method_codec_encode_error_envelope(FlMethodCodec* codec,
                                                       const gchar* code,
                                                       const gchar* message,
                                                       FlValue* details,
                                                       GError** error) {
  g_set_error(error, FL_MESSAGE_CODEC_ERROR, FL_MESSAGE_CODEC_ERROR_FAILED,
              "Unsupported type");
  return nullptr;
}

// Implements FlMethodCodec::encode_decode_response.
static FlMethodResponse* test_method_codec_decode_response(FlMethodCodec* codec,
                                                           GBytes* message,
                                                           GError** error) {
  EXPECT_TRUE(TEST_IS_METHOD_CODEC(codec));
  TestMethodCodec* self = TEST_METHOD_CODEC(codec);
  return fl_method_codec_decode_response(FL_METHOD_CODEC(self->wrapped_codec),
                                         message, error);
}

static void test_method_codec_class_init(TestMethodCodecClass* klass) {
  G_OBJECT_CLASS(klass)->dispose = test_method_codec_dispose;
  FL_METHOD_CODEC_CLASS(klass)->encode_method_call =
      test_method_codec_encode_method_call;
  FL_METHOD_CODEC_CLASS(klass)->decode_method_call =
      test_method_codec_decode_method_call;
  FL_METHOD_CODEC_CLASS(klass)->encode_success_envelope =
      test_method_codec_encode_success_envelope;
  FL_METHOD_CODEC_CLASS(klass)->encode_error_envelope =
      test_method_codec_encode_error_envelope;
  FL_METHOD_CODEC_CLASS(klass)->decode_response =
      test_method_codec_decode_response;
}

static void test_method_codec_init(TestMethodCodec* self) {
  self->wrapped_codec = fl_standard_method_codec_new();
}

TestMethodCodec* test_method_codec_new() {
  return TEST_METHOD_CODEC(g_object_new(test_method_codec_get_type(), nullptr));
}

// Called when a method call is received from the engine in the
// ReceiveMethodCallRespondSuccessError test.
static void method_call_success_error_cb(FlMethodChannel* channel,
                                         FlMethodCall* method_call,
                                         gpointer user_data) {
  g_autoptr(FlValue) result = fl_value_new_int(42);
  g_autoptr(GError) response_error = nullptr;
  EXPECT_FALSE(
      fl_method_call_respond_success(method_call, result, &response_error));
  EXPECT_NE(response_error, nullptr);

  // Respond to stop a warning occurring about not responding.
  fl_method_call_respond_not_implemented(method_call, nullptr);

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks error correctly handled if provide an unsupported arg in a method call
// response.
TEST(FlMethodChannelTest, ReceiveMethodCallRespondSuccessError) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(TestMethodCodec) codec = test_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(
      channel, method_call_success_error_cb, loop, nullptr);

  // Trigger the engine to make a method call.
  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
  fl_value_append_take(args, fl_value_new_string("Foo"));
  fl_value_append_take(args, fl_value_new_string("Marco!"));
  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
                                  nullptr, loop);

  // Blocks here until method_call_success_error_cb is called.
  g_main_loop_run(loop);
}

// Called when a method call is received from the engine in the
// ReceiveMethodCallRespondErrorError test.
static void method_call_error_error_cb(FlMethodChannel* channel,
                                       FlMethodCall* method_call,
                                       gpointer user_data) {
  g_autoptr(FlValue) details = fl_value_new_int(42);
  g_autoptr(GError) response_error = nullptr;
  EXPECT_FALSE(fl_method_call_respond_error(method_call, "error", "ERROR",
                                            details, &response_error));
  EXPECT_NE(response_error, nullptr);

  // Respond to stop a warning occurring about not responding.
  fl_method_call_respond_not_implemented(method_call, nullptr);

  g_main_loop_quit(static_cast<GMainLoop*>(user_data));
}

// Checks error correctly handled if provide an unsupported arg in a method call
// response.
TEST(FlMethodChannelTest, ReceiveMethodCallRespondErrorError) {
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);

  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(TestMethodCodec) codec = test_method_codec_new();
  g_autoptr(FlMethodChannel) channel = fl_method_channel_new(
      messenger, "test/standard-method", FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel, method_call_error_error_cb,
                                            loop, nullptr);

  // Trigger the engine to make a method call.
  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string("test/standard-method"));
  fl_value_append_take(args, fl_value_new_string("Foo"));
  fl_value_append_take(args, fl_value_new_string("Marco!"));
  fl_method_channel_invoke_method(channel, "InvokeMethod", args, nullptr,
                                  nullptr, loop);

  // Blocks here until method_call_error_error_cb is called.
  g_main_loop_run(loop);
}

struct UserDataReassignMethod {
  GMainLoop* loop;
  int count;
};

// This callback parses the user data as UserDataReassignMethod,
// increases its `count`, and quits `loop`.
static void reassign_method_cb(FlMethodChannel* channel,
                               FlMethodCall* method_call,
                               gpointer raw_user_data) {
  UserDataReassignMethod* user_data =
      static_cast<UserDataReassignMethod*>(raw_user_data);
  user_data->count += 1;

  g_autoptr(FlValue) result = fl_value_new_string("Polo!");
  g_autoptr(GError) error = nullptr;
  EXPECT_TRUE(fl_method_call_respond_success(method_call, result, &error));
  EXPECT_EQ(error, nullptr);

  g_main_loop_quit(user_data->loop);
}

// Make sure that the following steps will work properly:
//
// 1. Register a method channel.
// 2. Dispose the method channel, and it's unregistered.
// 3. Register a new channel with the same name.
//
// This is a regression test to https://github.com/flutter/flutter/issues/90817.
TEST(FlMethodChannelTest, ReplaceADisposedMethodChannel) {
  const char* method_name = "test/standard-method";
  // The loop is used to pause the main process until the callback is fully
  // executed.
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();

  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string(method_name));
  fl_value_append_take(args, fl_value_new_string("FOO"));
  fl_value_append_take(args, fl_value_new_string("BAR"));

  // Register the first channel and test if it works.
  UserDataReassignMethod user_data1{
      .loop = loop,
      .count = 100,
  };
  FlMethodChannel* channel1 =
      fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel1, reassign_method_cb,
                                            &user_data1, nullptr);

  fl_method_channel_invoke_method(channel1, "InvokeMethod", args, nullptr,
                                  nullptr, nullptr);
  g_main_loop_run(loop);
  EXPECT_EQ(user_data1.count, 101);

  // Dispose the first channel.
  g_object_unref(channel1);

  // Register the second channel and test if it works.
  UserDataReassignMethod user_data2{
      .loop = loop,
      .count = 100,
  };
  g_autoptr(FlMethodChannel) channel2 =
      fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel2, reassign_method_cb,
                                            &user_data2, nullptr);

  fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr,
                                  nullptr, nullptr);
  g_main_loop_run(loop);

  EXPECT_EQ(user_data1.count, 101);
  EXPECT_EQ(user_data2.count, 101);
}

// Make sure that the following steps will work properly:
//
// 1. Register a method channel.
// 2. Register the same name with a new channel.
// 3. Dispose the previous method channel.
//
// This is a regression test to https://github.com/flutter/flutter/issues/90817.
TEST(FlMethodChannelTest, DisposeAReplacedMethodChannel) {
  const char* method_name = "test/standard-method";
  // The loop is used to pause the main process until the callback is fully
  // executed.
  g_autoptr(GMainLoop) loop = g_main_loop_new(nullptr, 0);
  g_autoptr(FlEngine) engine = make_mock_engine();
  FlBinaryMessenger* messenger = fl_binary_messenger_new(engine);
  g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();

  g_autoptr(FlValue) args = fl_value_new_list();
  fl_value_append_take(args, fl_value_new_string(method_name));
  fl_value_append_take(args, fl_value_new_string("FOO"));
  fl_value_append_take(args, fl_value_new_string("BAR"));

  // Register the first channel and test if it works.
  UserDataReassignMethod user_data1{
      .loop = loop,
      .count = 100,
  };
  FlMethodChannel* channel1 =
      fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel1, reassign_method_cb,
                                            &user_data1, nullptr);

  fl_method_channel_invoke_method(channel1, "InvokeMethod", args, nullptr,
                                  nullptr, nullptr);
  g_main_loop_run(loop);
  EXPECT_EQ(user_data1.count, 101);

  // Register a new channel to the same name.
  UserDataReassignMethod user_data2{
      .loop = loop,
      .count = 100,
  };
  g_autoptr(FlMethodChannel) channel2 =
      fl_method_channel_new(messenger, method_name, FL_METHOD_CODEC(codec));
  fl_method_channel_set_method_call_handler(channel2, reassign_method_cb,
                                            &user_data2, nullptr);

  fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr,
                                  nullptr, nullptr);
  g_main_loop_run(loop);
  EXPECT_EQ(user_data1.count, 101);
  EXPECT_EQ(user_data2.count, 101);

  // Dispose the first channel. The new channel should keep working.
  g_object_unref(channel1);

  fl_method_channel_invoke_method(channel2, "InvokeMethod", args, nullptr,
                                  nullptr, nullptr);
  g_main_loop_run(loop);
  EXPECT_EQ(user_data1.count, 101);
  EXPECT_EQ(user_data2.count, 102);
}
