From 4412eebf0f7cf5b5091ba081762d5aa53fd1ec9b Mon Sep 17 00:00:00 2001 From: Gabriele Ghio Date: Fri, 29 Mar 2024 12:27:19 +0100 Subject: [PATCH] trigger_engine: added trigger_id and trigger_name to http events Changed - send_simple_event - retrieve_trigger_configuration to integrate trigger_id and trigger_name inside payload - using exandra -- waiting for database project - implemented jwt + public/private key - wip group tests - squashed all failure - polishing - removed one_of_constant -> member_of FIX #692 Signed-off-by: Gabriele Ghio --- apps/astarte_appengine_api/mix.exs | 5 +- apps/astarte_appengine_api/mix.lock | 9 +- .../astarte_appengine_api/v2_group_test.exs | 156 +++++++ .../test/astarte_appengine_api/v2_test.exs | 52 +++ .../test/support_v2/cases/conn.ex | 22 + .../test/support_v2/cases/database.ex | 17 + .../test/support_v2/common.ex | 0 .../test/support_v2/generators/device.ex | 24 ++ .../test/support_v2/generators/entites_OLD.ex | 59 +++ .../test/support_v2/generators/group.ex | 43 ++ .../test/support_v2/generators/interface.ex | 108 +++++ .../test/support_v2/generators/mapping.ex | 95 +++++ .../test/support_v2/generators/number.ex | 11 + .../test/support_v2/generators/string.ex | 37 ++ .../test/support_v2/helpers/database.ex | 380 ++++++++++++++++++ .../test/support_v2/helpers/jwt.ex | 48 +++ .../test/support_v2/setups/conn.ex | 22 + .../test/support_v2/setups/database.ex | 35 ++ .../test/support_v2/setups/interface.ex | 18 + 19 files changed, 1136 insertions(+), 5 deletions(-) create mode 100644 apps/astarte_appengine_api/test/astarte_appengine_api/v2_group_test.exs create mode 100644 apps/astarte_appengine_api/test/astarte_appengine_api/v2_test.exs create mode 100644 apps/astarte_appengine_api/test/support_v2/cases/conn.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/cases/database.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/common.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/generators/device.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/generators/entites_OLD.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/generators/group.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/generators/interface.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/generators/mapping.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/generators/number.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/generators/string.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/helpers/database.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/helpers/jwt.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/setups/conn.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/setups/database.ex create mode 100644 apps/astarte_appengine_api/test/support_v2/setups/interface.ex diff --git a/apps/astarte_appengine_api/mix.exs b/apps/astarte_appengine_api/mix.exs index d88c59ecf..3ee7557e0 100644 --- a/apps/astarte_appengine_api/mix.exs +++ b/apps/astarte_appengine_api/mix.exs @@ -48,7 +48,7 @@ defmodule Astarte.AppEngine.API.Mixfile do end # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["test/support", "lib"] + defp elixirc_paths(:test), do: ["test/support", "test/support_v2", "lib"] defp elixirc_paths(_), do: ["lib"] defp dialyzer_cache_directory(:ci) do @@ -93,7 +93,8 @@ defmodule Astarte.AppEngine.API.Mixfile do # Required by :phoenix_swagger, otherwise it fails finding ex_json_schema.app {:ex_json_schema, "~> 0.7"}, {:phoenix_swagger, "~> 0.8"}, - {:xandra, "~> 0.13"}, + {:xandra, "~> 0.18"}, + {:exandra, "~> 0.10.2"}, {:pretty_log, "~> 0.1"}, {:plug_logger_with_meta, "~> 0.1"}, {:telemetry, "~> 0.4"}, diff --git a/apps/astarte_appengine_api/mix.lock b/apps/astarte_appengine_api/mix.lock index 7423d03f5..bb2e7f1f4 100644 --- a/apps/astarte_appengine_api/mix.lock +++ b/apps/astarte_appengine_api/mix.lock @@ -15,16 +15,18 @@ "cqex": {:hex, :cqex, "1.0.1", "bc9980ac3b82d039879f8d6ca589deab799fe08f80ff449d60ad709f2524718f", [:mix], [{:cqerl, "~> 2.0.1", [hex: :cqerl, repo: "hexpm", optional: false]}], "hexpm", "1bbf2079c044cbf0f747f60dcf0409a951eaa8f1a2447cd6d80d6ff1b7c4dc6b"}, "credentials_obfuscation": {:hex, :credentials_obfuscation, "3.4.0", "34e18b126b3aefd6e8143776fbe1ceceea6792307c99ac5ee8687911f048cfd7", [:rebar3], [], "hexpm", "738ace0ed5545d2710d3f7383906fc6f6b582d019036e5269c4dbd85dbced566"}, "cyanide": {:hex, :cyanide, "2.0.0", "f97b700b87f9b0679ae812f0c4b7fe35ea6541a4121a096cf10287941b7a6d55", [:mix], [], "hexpm", "7f9748251804c2a2115b539202568e1117ab2f0ae09875853fb89cc94ae19dd1"}, - "db_connection": {:hex, :db_connection, "2.3.1", "4c9f3ed1ef37471cbdd2762d6655be11e38193904d9c5c1c9389f1b891a3088e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "abaab61780dde30301d840417890bd9f74131041afd02174cf4e10635b3a63f5"}, - "decimal": {:hex, :decimal, "1.9.0", "83e8daf59631d632b171faabafb4a9f4242c514b0a06ba3df493951c08f64d07", [:mix], [], "hexpm", "b1f2343568eed6928f3e751cf2dffde95bfaa19dd95d09e8a9ea92ccfd6f7d85"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, "dialyzex": {:git, "https://github.com/Comcast/dialyzex.git", "cdc7cf71fe6df0ce4cf59e3f497579697a05c989", []}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_morph": {:hex, :ecto_morph, "0.1.28", "073c3faf4ff7c496fc2ae8352ea7d8c9b89ae7b2e6995da3027454d44cb547fb", [:mix], [{:ecto, ">= 3.0.3", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "ce39a1252a5b7d58c601beb702eed4303b11016705f02fbae9b78f8fb971b0bc"}, + "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_json_schema": {:hex, :ex_json_schema, "0.7.4", "09eb5b0c8184e5702bc89625a9d0c05c7a0a845d382e9f6f406a0fc1c9a8cc3f", [:mix], [], "hexpm", "45c67fa840f0d719a2b5578126dc29bcdc1f92499c0f61bcb8a3bcb5935f9684"}, "ex_lttb": {:hex, :ex_lttb, "0.3.0", "aec7aab96be6535c4c8f143c2b5f2191a9d1a6f512690ec6d6f4f6bce0223b0f", [:mix], [], "hexpm", "6937bf70307d85781200912c3dcf5e32efdcbdf958e9107ee0a61d4fefc1fddb"}, + "exandra": {:hex, :exandra, "0.10.2", "e95dca77501df9ae48f23854224e91712e64d65cd7157e2fe46232ea97918ec6", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:xandra, "~> 0.18.0", [hex: :xandra, repo: "hexpm", optional: false]}], "hexpm", "334616b170233828f2acac0b060c3c6c91051848972218d2b159e3a455b07c84"}, "excoveralls": {:hex, :excoveralls, "0.15.0", "ac941bf85f9f201a9626cc42b2232b251ad8738da993cf406a4290cacf562ea4", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9631912006b27eca30a2f3c93562bc7ae15980afb014ceb8147dc5cdd8f376f1"}, "expo": {:hex, :expo, "0.5.2", "beba786aab8e3c5431813d7a44b828e7b922bfa431d6bfbada0904535342efe2", [:mix], [], "hexpm", "8c9bfa06ca017c9cb4020fabe980bc7fdb1aaec059fd004c2ab3bff03b1c599c"}, "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, @@ -41,6 +43,7 @@ "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mox": {:hex, :mox, "0.5.2", "55a0a5ba9ccc671518d068c8dddd20eeb436909ea79d1799e2209df7eaa98b6c", [:mix], [], "hexpm", "df4310628cd628ee181df93f50ddfd07be3e5ecc30232d3b6aadf30bdfe6092b"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "observer_cli": {:hex, :observer_cli, "1.6.1", "d176f967c978ab8b8a29c35c12524f78b7bb36fd4e9b8276dd75c9cb56e07e42", [:mix, :rebar3], [{:recon, "~>2.5.1", [hex: :recon, repo: "hexpm", optional: false]}], "hexpm", "3418e319764b9dff1f469e43cbdffd7fd54ea47cbf765027c557abd146a19fb3"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "phoenix": {:hex, :phoenix, "1.7.2", "c375ffb482beb4e3d20894f84dd7920442884f5f5b70b9f4528cbe0cedefec63", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.4", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1ebca94b32b4d0e097ab2444a9742ed8ff3361acad17365e4e6b2e79b4792159"}, @@ -74,5 +77,5 @@ "uuid": {:hex, :uuid_erl, "2.0.1", "1fd9079c544d521063897887a1c5b3302dca98f9bb06aadcdc6fb0663f256797", [:rebar3], [{:quickrand, "~> 2.0.1", [hex: :quickrand, repo: "hexpm", optional: false]}], "hexpm", "ab57caccd51f170011e5f444ce865f84b41605e483a9efcc468c1afaec87553b"}, "websock": {:hex, :websock, "0.5.1", "c496036ce95bc26d08ba086b2a827b212c67e7cabaa1c06473cd26b40ed8cf10", [:mix], [], "hexpm", "b9f785108b81cd457b06e5f5dabe5f65453d86a99118b2c0a515e1e296dc2d2c"}, "websock_adapter": {:hex, :websock_adapter, "0.5.1", "292e6c56724e3457e808e525af0e9bcfa088cc7b9c798218e78658c7f9b85066", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "8e2e1544bfde5f9d0442f9cec2f5235398b224f75c9e06b60557debf64248ec1"}, - "xandra": {:hex, :xandra, "0.13.1", "f82866e6c47527f74f35dd3007b5311121852dd861a29ed1613e27ccfaba0102", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.7", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "a2efdb8921e3b694bf3505e40c5ec9d353d8fa3755cec946be7c18b8236d7230"}, + "xandra": {:hex, :xandra, "0.18.1", "6ac8794161f69a5ada6e8c197e5e3472f44c94f7b3add208cd3abc8ee135f852", [:mix], [{:decimal, "~> 1.7 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "25d74d8101ca303b7be102da14a37629ae94c1bd21827f9d199a27f5e89b785f"}, } diff --git a/apps/astarte_appengine_api/test/astarte_appengine_api/v2_group_test.exs b/apps/astarte_appengine_api/test/astarte_appengine_api/v2_group_test.exs new file mode 100644 index 000000000..37483c266 --- /dev/null +++ b/apps/astarte_appengine_api/test/astarte_appengine_api/v2_group_test.exs @@ -0,0 +1,156 @@ +defmodule Astarte.AppEngine.API.V2GroupTest do + use ExUnit.Case, async: true + use ExUnitProperties + use Astarte.Test.Cases.Database + use Astarte.Test.Cases.Conn + + alias Astarte.Test.Setups.Database, as: DatabaseSetup + alias Astarte.Test.Setups.Conn, as: ConnSetup + alias Astarte.Test.Generators.Group, as: GroupGenerator + alias Astarte.Test.Generators.Device, as: DeviceGenerator + + @moduletag :v2 + @moduletag :group + + describe "create" do + property "failed", %{auth_conn: auth_conn, realm: realm} do + invalid_group_name_generator = + gen all name <- GroupGenerator.name(), + choice <- integer(0..2) do + case choice do + 0 -> "" + 1 -> "~" <> name + 2 -> "@" <> name + end + end + + group_results_generator = + gen all name <- one_of([GroupGenerator.name(), invalid_group_name_generator]), + devices <- list_of(DeviceGenerator.encoded_id(), min_length: 0, max_length: 1) do + {name, devices, + cond do + "" === name -> + {"group_name", "can't be blank"} + + String.first(name) in ["@", "~"] -> + {"group_name", "is not valid"} + + length(devices) === 0 -> + {"devices", "should have at least 1 item(s)"} + + true -> + {"devices", "must exist (#{Enum.at(devices, 0)} not found)"} + end} + end + + check all {group_name, devices, {field, error}} <- group_results_generator do + params = %{ + "group_name" => group_name, + "devices" => devices + } + + response = post(auth_conn, groups_path(auth_conn, :create, realm), data: params) + assert [error] === json_response(response, 422)["errors"][field] + end + end + end + + # describe "devices checks" do + # # TODO devices + # # setup [ + # # {DeviceSetup, :init}, + # # {DeviceSetup, :setup} + # # ] + + # property "rejects empty devices", %{auth_conn: auth_conn, realm: realm} do + # check all group_name <- GroupGenerator.group_name() do + # params = %{ + # "group_name" => group_name, + # "devices" => [] + # } + + # response = post(auth_conn, groups_path(auth_conn, :create, realm), data: params) + + # assert ["should have at least 1 item(s)"] === + # json_response(response, 422)["errors"]["devices"] + # end + # end + # end + + # describe "create" do + # + + # test "doesn't create group if a device doesn't exist", %{conn: conn} do + # params = %{ + # "group_name" => @group_name, + # "devices" => [ + # "2uL21mYBQsWVik8declWQQ" + # | @group_devices + # ] + # } + + # create_conn = post(conn, groups_path(conn, :create, @realm), data: params) + + # assert json_response(create_conn, 422)["errors"]["devices"] != nil + + # show_conn = get(conn, groups_path(conn, :show, @realm, @group_name)) + + # assert json_response(show_conn, 404) + # end + + # test "creates the group with valid parameters", %{conn: conn} do + # params = %{ + # "group_name" => @group_name, + # "devices" => @group_devices + # } + + # create_conn = post(conn, groups_path(conn, :create, @realm), data: params) + + # assert json_response(create_conn, 201)["data"] == params + + # show_conn = get(conn, groups_path(conn, :show, @realm, @group_name)) + + # assert json_response(show_conn, 200)["data"]["group_name"] == @group_name + + # for device <- @group_devices do + # {:ok, %DeviceStatus{groups: groups}} = Device.get_device_status!(@realm, device) + # assert groups == [@group_name] + # end + # end + + # test "rejects an already existing group", %{conn: conn} do + # params = %{ + # "group_name" => @group_name, + # "devices" => @group_devices + # } + + # create_conn = post(conn, groups_path(conn, :create, @realm), data: params) + + # assert json_response(create_conn, 201)["data"] == params + + # create_again_conn = post(conn, groups_path(conn, :create, @realm), data: params) + + # assert json_response(create_again_conn, 409)["errors"] != nil + # end + + # @tag issue: 904 + # property "creates the group with / in group name", %{conn: conn} do + # check all group_name <- GroupTestGenerator.group_name() do + # params = %{ + # "group_name" => group_name, + # "devices" => @group_devices + # } + + # create_conn = post(conn, groups_path(conn, :create, @realm), data: params) + + # assert json_response(create_conn, 201)["data"] == params, + # "Failed to create group #{group_name}" + + # show_conn = get(conn, groups_path(conn, :show, @realm, group_name)) + + # assert json_response(show_conn, 200)["data"]["group_name"] == group_name, + # "Failed post/get same group_name #{group_name}" + # end + # end + # end +end diff --git a/apps/astarte_appengine_api/test/astarte_appengine_api/v2_test.exs b/apps/astarte_appengine_api/test/astarte_appengine_api/v2_test.exs new file mode 100644 index 000000000..d0e00b3d9 --- /dev/null +++ b/apps/astarte_appengine_api/test/astarte_appengine_api/v2_test.exs @@ -0,0 +1,52 @@ +defmodule Astarte.AppEngine.API.V2Test do + use ExUnit.Case, async: true + use ExUnitProperties + use Astarte.Test.Cases.Database + alias Ecto.Changeset + alias StreamData + alias Astarte.Core.Mapping + alias Astarte.Core.Interface + alias Astarte.AppEngine.API.Stats + alias Astarte.AppEngine.API.Stats.DevicesStats + alias Astarte.Test.Setups.Database, as: DatabaseSetup + alias Astarte.Test.Setups.Interface, as: InterfaceSetup + alias Astarte.Test.Generators.String, as: StringGenerator + alias Astarte.Test.Generators.Interface, as: InterfaceGenerator + alias Astarte.Test.Generators.Mapping, as: MappingGenerator + alias Astarte.Test.Generators.Device, as: DeviceGenerator + alias Astarte.Test.Helpers.Database, as: DatabaseHelper + + @moduletag :v2 + describe "interface generator" do + @tag timeout: :infinity + property "validate interface" do + check all interface <- InterfaceGenerator.interface() do + %Changeset{valid?: valid, errors: errors} = Interface.changeset(interface) + + assert valid, "Invalid interface: " <> (errors |> Enum.join(", ")) + end + end + end + + describe "interfaces db" do + setup [ + {InterfaceSetup, :init}, + {InterfaceSetup, :setup} + ] + + @tag interface_count: 100 + test "validate insered interfaces names", %{ + cluster: cluster, + realm: realm, + interfaces: interfaces + } do + list = DatabaseHelper.select!(:interface, cluster, realm, interfaces) + f = fn l -> Enum.map(l, fn i -> i.name end) end + interfaces_names_a = f.(interfaces) + interfaces_names_b = f.(list) + + assert [] === interfaces_names_a -- interfaces_names_b + assert [] === interfaces_names_b -- interfaces_names_a + end + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/cases/conn.ex b/apps/astarte_appengine_api/test/support_v2/cases/conn.ex new file mode 100644 index 000000000..f193e8445 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/cases/conn.ex @@ -0,0 +1,22 @@ +defmodule Astarte.Test.Cases.Conn do + use ExUnit.CaseTemplate + + using do + quote do + import Plug.Conn + import Phoenix.ConnTest + import Astarte.AppEngine.APIWeb.Router.Helpers + + # The default endpoint for testing + @endpoint Astarte.AppEngine.APIWeb.Endpoint + end + end + + alias Astarte.Test.Setups.Conn, as: ConnSetup + + setup_all [ + {ConnSetup, :create_conn}, + {ConnSetup, :jwt}, + {ConnSetup, :auth_conn} + ] +end diff --git a/apps/astarte_appengine_api/test/support_v2/cases/database.ex b/apps/astarte_appengine_api/test/support_v2/cases/database.ex new file mode 100644 index 000000000..2bac8898d --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/cases/database.ex @@ -0,0 +1,17 @@ +defmodule Astarte.Test.Cases.Database do + use ExUnit.CaseTemplate + + using do + quote do + end + end + + alias Astarte.Test.Setups.Database, as: DatabaseSetup + + setup_all [ + {DatabaseSetup, :connect}, + {DatabaseSetup, :realm}, + {DatabaseSetup, :setup}, + {DatabaseSetup, :setup_auth} + ] +end diff --git a/apps/astarte_appengine_api/test/support_v2/common.ex b/apps/astarte_appengine_api/test/support_v2/common.ex new file mode 100644 index 000000000..e69de29bb diff --git a/apps/astarte_appengine_api/test/support_v2/generators/device.ex b/apps/astarte_appengine_api/test/support_v2/generators/device.ex new file mode 100644 index 000000000..684842dfb --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/generators/device.ex @@ -0,0 +1,24 @@ +defmodule Astarte.Test.Generators.Device do + use ExUnitProperties + + def id() do + gen all seq <- binary(length: 16) do + <> = seq + <> + end + end + + def encoded_id() do + gen all id <- id() do + Base.url_encode64(id, padding: false) + end + end + + def device() do + gen all id <- id() do + %{ + id: id + } + end + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/generators/entites_OLD.ex b/apps/astarte_appengine_api/test/support_v2/generators/entites_OLD.ex new file mode 100644 index 000000000..c26aa13ac --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/generators/entites_OLD.ex @@ -0,0 +1,59 @@ +defmodule Astarte.Test.Generators.Entities_OLD do + use ExUnitProperties + + def domain() do + gen all first <- string([?a..?z], length: 3), + second <- string([?a..?z, ?A..?Z], min_length: 1, max_length: 10), + third <- string([?a..?z, ?A..?Z], min_length: 1, max_length: 10) do + first <> "." <> second <> "." <> third + end + end + + def interface() do + gen all domain <- domain(), + enabled <- boolean() do + {domain, enabled} + end + end + + def interface_value() do + gen all interface <- interface(), + value <- integer(1..100_100) do + {interface, value} + end + end + + def interfaces() do + gen all interface_value <- list_of(interface_value()), + num <- integer(0..20) do + Enum.take(interface_value, num) |> Map.new() + end + end + + def options do + optional_map(%{ + display_name: string(:alphanumeric), + serial: string(?0..?9, min_length: 1, max_length: 10) + }) + end + + def attributes do + fixed_map(%{ + attribute_key: string(:alphanumeric) + }) + end + + def device() do + gen all id <- string(:alphanumeric, length: 22), + value <- integer(0..8_000_000), + interfaces <- one_of([nil, interfaces()]), + options <- one_of([nil, options()]), + attributes <- attributes() do + {id, value, interfaces, options, attributes} + end + end + + def devices() do + list_of(device()) + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/generators/group.ex b/apps/astarte_appengine_api/test/support_v2/generators/group.ex new file mode 100644 index 000000000..570d9af00 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/generators/group.ex @@ -0,0 +1,43 @@ +# +# This file is part of Astarte. +# +# Copyright 2024 SECO Mind Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +defmodule Astarte.Test.Generators.Group do + @moduledoc """ + Helpers group's generators + """ + use ExUnitProperties + + @max_subpath_count 10 + @max_subpath_length 20 + + @doc """ + Generate a random group name + Es. + world/europe/italy + """ + def name() do + string(:ascii, min_length: 1, max_length: @max_subpath_length) + |> uniq_list_of( + min_length: 1, + max_length: @max_subpath_count + ) + |> map(&Enum.join(&1, "/")) + |> filter(fn name -> + String.first(name) not in ["@", "~", "\s"] + end) + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/generators/interface.ex b/apps/astarte_appengine_api/test/support_v2/generators/interface.ex new file mode 100644 index 000000000..e4b584198 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/generators/interface.ex @@ -0,0 +1,108 @@ +defmodule Astarte.Test.Generators.Interface do + use ExUnitProperties + + alias Ecto.UUID + alias Astarte.Core.Interface + alias Astarte.Test.Generators.String, as: StringGenerator + alias Astarte.Test.Generators.Mapping, as: MappingGenerator + + defp id() do + repeatedly(&UUID.generate/0) + end + + defp name() do + string(:alphanumeric, min_length: 1, max_length: 16) + |> list_of( + min_length: 2, + max_length: 5 + ) + |> filter(fn [<> | _] -> + first < 48 or first > 57 + end) + |> map(&Enum.join(&1, ".")) + end + + defp versions() do + gen all major_version <- integer(0..9), + minor_version <- integer(0..255) do + case {major_version, minor_version} do + {0, 0} -> {0, 1} + valid -> valid + end + end + end + + defp type(), do: member_of([:datastream, :properties]) + + defp ownership(), do: member_of([:device, :server]) + + defp mappings(config) do + uniq_list_of(MappingGenerator.mapping(config), min_length: 1, max_length: 1000) + end + + defp aggregation(%{type: :properties}), do: constant(:individual) + defp aggregation(_), do: member_of([:individual, :object]) + + defp description() do + string(:ascii, min_length: 1, max_length: 1000) + end + + defp doc() do + string(:ascii, min_length: 1, max_length: 100_000) + end + + defp required_fields() do + gen all id <- id(), + name <- name(), + {major_version, minor_version} <- versions(), + type <- type(), + aggregation <- aggregation(%{type: type}), + ownership <- ownership(), + prefix <- StringGenerator.endpoint_prefix(), + retention <- MappingGenerator.retention(), + reliability <- MappingGenerator.reliability(), + expiry <- MappingGenerator.expiry(), + allow_unset <- MappingGenerator.allow_unset(), + explicit_timestamp <- MappingGenerator.explicit_timestamp(), + mappings <- + mappings(%{ + aggregation: aggregation, + prefix: prefix, + retention: retention, + reliability: reliability, + expiry: expiry, + allow_unset: allow_unset, + explicit_timestamp: explicit_timestamp + }) do + %{ + id: id, + interface_id: id, + name: name, + interface_name: name, + major_version: major_version, + minor_version: minor_version, + version_major: major_version, + version_minor: minor_version, + type: type, + interface_type: type, + ownership: ownership, + aggregation: aggregation, + mappings: mappings + } + end + end + + defp optional_fields() do + optional_map(%{ + description: description(), + doc: doc() + }) + end + + def interface() do + gen all required <- required_fields(), + optional <- optional_fields() do + struct(Interface, Map.merge(required, optional)) + end + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/generators/mapping.ex b/apps/astarte_appengine_api/test/support_v2/generators/mapping.ex new file mode 100644 index 000000000..a27a09cba --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/generators/mapping.ex @@ -0,0 +1,95 @@ +defmodule Astarte.Test.Generators.Mapping do + use ExUnitProperties + + alias Astarte.Core.Mapping + + defp endpoint(%{aggregation: aggregation, prefix: prefix}) do + # TODO: thinking about it + generator = + case aggregation do + :individual -> repeatedly(fn -> "/individual_#{System.unique_integer([:positive])}" end) + :object -> repeatedly(fn -> "/object_#{System.unique_integer([:positive])}" end) + end + + gen all postfix <- generator do + prefix <> postfix + end + end + + defp type() do + member_of([ + :double, + :integer, + :boolean, + :longinteger, + :string, + :binaryblob, + :datetime, + :doublearray, + :integerarray, + :booleanarray, + :longintegerarray, + :stringarray, + :binaryblobarray, + :datetimearray + ]) + end + + def reliability(), do: member_of([:unreliable, :guaranteed, :unique]) + + def explicit_timestamp(), do: boolean() + + def retention(), do: member_of([:discard, :volatile, :stored]) + + def expiry(), do: one_of([constant(0), integer(1..10_000)]) + + def database_retention_policy(), do: member_of([:no_ttl, :use_ttl]) + + def database_retention_ttl(), do: integer(0..10_1000) + + def allow_unset(), do: boolean() + + defp description() do + string(:ascii, min_length: 1, max_length: 1000) + end + + defp doc() do + string(:ascii, min_length: 1, max_length: 100_000) + end + + defp required_fields(%{ + aggregation: aggregation, + prefix: prefix, + retention: retention, + reliability: reliability, + explicit_timestamp: explicit_timestamp, + allow_unset: allow_unset, + expiry: expiry + }) do + fixed_map(%{ + endpoint: endpoint(%{aggregation: aggregation, prefix: prefix}), + type: type(), + retention: constant(retention), + reliability: constant(reliability), + explicit_timestamp: constant(explicit_timestamp), + allow_unset: constant(allow_unset), + expiry: constant(expiry) + }) + end + + defp optional_fields(_config) do + optional_map(%{ + database_retention_policy: database_retention_policy(), + database_retention_ttl: database_retention_ttl(), + description: description(), + doc: doc() + }) + end + + def mapping(config) do + gen all required <- required_fields(config), + optional <- optional_fields(config) do + struct(Mapping, Map.merge(required, optional)) + end + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/generators/number.ex b/apps/astarte_appengine_api/test/support_v2/generators/number.ex new file mode 100644 index 000000000..88a3d6e49 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/generators/number.ex @@ -0,0 +1,11 @@ +defmodule Astarte.Test.Generators.Number do + use ExUnitProperties + + def random_numbers(max) do + gen all first <- StreamData.integer(0..max), + second <- StreamData.integer(0..max), + third <- StreamData.integer(0..max) do + {first, second, third} + end + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/generators/string.ex b/apps/astarte_appengine_api/test/support_v2/generators/string.ex new file mode 100644 index 000000000..0b1f08960 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/generators/string.ex @@ -0,0 +1,37 @@ +defmodule Astarte.Test.Generators.String do + use ExUnitProperties + alias StreamData + + def random_string(max) do + string(:alphanumeric, length: max) + end + + def realm_name() do + repeatedly(fn -> "realm_#{System.unique_integer([:positive, :monotonic])}" end) + end + + def endpoint_subpath() do + string([?a..?z, ?_], min_length: 1, max_length: 5) + end + + def endpoint_parametric_subpath() do + gen all subpath <- endpoint_subpath() do + "%{" <> subpath <> "}" + end + end + + def endpoint_prefix() do + gen all prefix <- + frequency([ + {1, endpoint_subpath()}, + {1, endpoint_parametric_subpath()} + ]) + |> list_of( + min_length: 1, + max_length: 5 + ) + |> map(&Enum.join(&1, "/")) do + "/" <> prefix + end + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/helpers/database.ex b/apps/astarte_appengine_api/test/support_v2/helpers/database.ex new file mode 100644 index 000000000..04261a2a3 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/helpers/database.ex @@ -0,0 +1,380 @@ +defmodule Astarte.Test.Helpers.Database do + alias Astarte.Core.Interface + alias Astarte.Core.Interface.Type, as: InterfaceType + alias Astarte.Core.Interface.Aggregation, as: AggregationType + alias Astarte.Core.Interface.Ownership, as: OwnershipType + + @create_realm """ + CREATE KEYSPACE :realm + WITH + replication = {'class': 'SimpleStrategy', 'replication_factor': '1'} AND + durable_writes = true; + """ + + @drop_realm """ + DROP KEYSPACE :realm; + """ + + @create_kv_store """ + CREATE TABLE :realm.kv_store ( + group varchar, + key varchar, + value blob, + + PRIMARY KEY ((group), key) + ); + """ + + @create_names_table """ + CREATE TABLE :realm.names ( + object_name varchar, + object_type int, + object_uuid uuid, + + PRIMARY KEY ((object_name), object_type) + ); + """ + + @create_devices_table """ + CREATE TABLE :realm.devices ( + device_id uuid, + aliases map, + introspection map, + introspection_minor map, + protocol_revision int, + first_registration timestamp, + credentials_secret ascii, + inhibit_credentials_request boolean, + cert_serial ascii, + cert_aki ascii, + first_credentials_request timestamp, + last_connection timestamp, + last_disconnection timestamp, + connected boolean, + pending_empty_cache boolean, + total_received_msgs bigint, + total_received_bytes bigint, + last_credentials_request_ip inet, + last_seen_ip inet, + + PRIMARY KEY (device_id) + ); + """ + + @create_interfaces_table """ + CREATE TABLE :realm.interfaces ( + name ascii, + major_version int, + minor_version int, + interface_id uuid, + storage_type int, + storage ascii, + type int, + ownership int, + aggregation int, + automaton_transitions blob, + automaton_accepting_states blob, + description varchar, + doc varchar, + + PRIMARY KEY (name, major_version) + ); + """ + + @create_endpoints_table """ + CREATE TABLE :realm.endpoints ( + interface_id uuid, + endpoint_id uuid, + interface_name ascii, + interface_major_version int, + interface_minor_version int, + interface_type int, + endpoint ascii, + value_type int, + reliability int, + retention int, + database_retention_policy int, + database_retention_ttl int, + expiry int, + allow_unset boolean, + explicit_timestamp boolean, + description varchar, + doc varchar, + + PRIMARY KEY ((interface_id), endpoint_id) + ); + """ + @create_individual_properties_table """ + CREATE TABLE :realm.individual_properties ( + device_id uuid, + interface_id uuid, + endpoint_id uuid, + path varchar, + reception_timestamp timestamp, + reception_timestamp_submillis smallint, + + double_value double, + integer_value int, + boolean_value boolean, + longinteger_value bigint, + string_value varchar, + binaryblob_value blob, + datetime_value timestamp, + doublearray_value list, + integerarray_value list, + booleanarray_value list, + longintegerarray_value list, + stringarray_value list, + binaryblobarray_value list, + datetimearray_value list, + + PRIMARY KEY((device_id, interface_id), endpoint_id, path) + ); + """ + + @create_individual_datastreams_table """ + CREATE TABLE IF NOT EXISTS :realm.individual_datastreams ( + device_id uuid, + interface_id uuid, + endpoint_id uuid, + path varchar, + value_timestamp timestamp, + reception_timestamp timestamp, + reception_timestamp_submillis smallint, + + double_value double, + integer_value int, + boolean_value boolean, + longinteger_value bigint, + string_value varchar, + binaryblob_value blob, + datetime_value timestamp, + doublearray_value list, + integerarray_value list, + booleanarray_value list, + longintegerarray_value list, + stringarray_value list, + binaryblobarray_value list, + datetimearray_value list, + + PRIMARY KEY((device_id, interface_id, endpoint_id, path), value_timestamp, reception_timestamp, reception_timestamp_submillis) + ); + """ + + @create_test_object_table """ + CREATE TABLE :realm.com_example_testobject_v1 ( + device_id uuid, + path varchar, + reception_timestamp timestamp, + v_string varchar, + v_value double, + PRIMARY KEY ((device_id, path), reception_timestamp) + ); + """ + + @insert_interfaces """ + INSERT INTO :realm.interfaces ( + name, + interface_id, + major_version, + minor_version, + type, + automaton_accepting_states, + automaton_transitions, + aggregation, + ownership, + storage, + storage_type + ) VALUES ( + :name, + :interface_id, + :major_version, + :minor_version, + :type, + :automaton_accepting_states, + :automaton_transitions, + :aggregation, + :ownership, + :storage, + :storage_type + ) + """ + + @select_interface """ + SELECT * FROM :realm.interfaces WHERE name IN :names + """ + + @delete_interface """ + DELETE FROM :realm.interfaces WHERE name = :name + """ + + @insert_devices """ + INSERT INTO :realm.devices + ( + device_id, + aliases, + attributes, + connected, + last_connection, + last_disconnection, + first_registration, + first_credentials_request, + last_seen_ip, + last_credentials_request_ip, + total_received_msgs, + total_received_bytes, + inhibit_credentials_request, + introspection, + introspection_minor, + exchanged_msgs_by_interface, + exchanged_bytes_by_interface + ) + VALUES + ( + :device_id, + :aliases, + :attributes, + :connected, + :last_connection, + :last_disconnection, + :first_registration, + :first_credentials_request, + :last_seen_ip, + :last_credentials_request_ip, + :total_received_msgs, + :total_received_bytes, + :inhibit_credentials_request, + :introspection, + :introspection_minor, + :exchanged_msgs_by_interface, + :exchanged_bytes_by_interface + ) + """ + + @insert_pubkeypem """ + INSERT INTO :realm.kv_store (group, key, value) + VALUES ('auth', 'jwt_public_key_pem', varcharAsBlob(:pem)); + """ + + @delete_pubkeypem """ + DELETE FROM :realm.kv_store WHERE group = 'auth' AND key = 'jwt_public_key_pem' + """ + + def create_test_keyspace!(cluster, realm) do + Xandra.Cluster.execute!(cluster, String.replace(@create_realm, ":realm", realm)) + + Xandra.Cluster.execute!(cluster, String.replace(@create_devices_table, ":realm", realm)) + Xandra.Cluster.execute!(cluster, String.replace(@create_names_table, ":realm", realm)) + Xandra.Cluster.execute!(cluster, String.replace(@create_kv_store, ":realm", realm)) + Xandra.Cluster.execute!(cluster, String.replace(@create_endpoints_table, ":realm", realm)) + + Xandra.Cluster.execute!( + cluster, + String.replace(@create_individual_properties_table, ":realm", realm) + ) + + Xandra.Cluster.execute!( + cluster, + String.replace(@create_individual_datastreams_table, ":realm", realm) + ) + + Xandra.Cluster.execute!( + cluster, + String.replace(@create_test_object_table, ":realm", realm) + ) + + Xandra.Cluster.execute!( + cluster, + String.replace(@create_interfaces_table, ":realm", realm) + ) + end + + def destroy_test_keyspace!(cluster, realm) do + Xandra.Cluster.execute!(cluster, String.replace(@drop_realm, ":realm", realm)) + end + + def insert!(:pubkeypem, cluster, realm, pub_key_pem) do + query = Xandra.Cluster.prepare!(cluster, String.replace(@insert_pubkeypem, ":realm", realm)) + + Xandra.Cluster.execute!(cluster, query, %{ + "pem" => pub_key_pem + }) + end + + def insert!(:interface, cluster, realm, interfaces) do + prepared = + Xandra.Cluster.prepare!(cluster, String.replace(@insert_interfaces, ":realm", realm)) + + batch = + Enum.reduce(interfaces, Xandra.Batch.new(), fn interface, acc -> + Xandra.Batch.add(acc, prepared, %{ + "name" => interface.name, + "interface_id" => interface.interface_id, + "major_version" => interface.major_version, + "minor_version" => interface.minor_version, + "type" => InterfaceType.to_int(interface.type), + "automaton_accepting_states" => :erlang.term_to_binary(:automaton_accepting_states), + "automaton_transitions" => :erlang.term_to_binary(:automaton_transitions), + "aggregation" => AggregationType.to_int(interface.aggregation), + # TODO Mapping to the other table + "ownership" => OwnershipType.to_int(interface.ownership), + "storage" => "individual_properties", + "storage_type" => 1 + }) + end) + + Xandra.Cluster.execute!(cluster, batch) + end + + def select!(:interface, cluster, realm, interfaces) do + prepared = + Xandra.Cluster.prepare!(cluster, String.replace(@select_interface, ":realm", realm)) + + list = + interfaces + |> Stream.map(fn %Interface{} = interface -> interface.name end) + |> Enum.to_list() + + %Xandra.Page{} = + page = + Xandra.Cluster.execute!( + cluster, + prepared, + %{ + "names" => list + } + ) + + Stream.map(page, fn record -> + %Interface{ + name: record["name"], + interface_id: record["interface_id"], + major_version: record["major_version"], + minor_version: record["minor_version"], + type: InterfaceType.from_int(record["type"]), + aggregation: AggregationType.from_int(record["aggregation"]), + # TODO Mapping from the other table + ownership: OwnershipType.from_int(record["ownership"]) + } + end) + |> Enum.to_list() + end + + def delete!(:pubkeypem, cluster, realm) do + Xandra.Cluster.execute!(cluster, String.replace(@delete_pubkeypem, ":realm", realm)) + end + + def delete!(:interface, cluster, realm, interfaces) do + prepared = + Xandra.Cluster.prepare!(cluster, String.replace(@delete_interface, ":realm", realm)) + + batch = + Enum.reduce(interfaces, Xandra.Batch.new(), fn interface, acc -> + Xandra.Batch.add(acc, prepared, %{ + "name" => interface.name + }) + end) + + Xandra.Cluster.execute!(cluster, batch) + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/helpers/jwt.ex b/apps/astarte_appengine_api/test/support_v2/helpers/jwt.ex new file mode 100644 index 000000000..a96498853 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/helpers/jwt.ex @@ -0,0 +1,48 @@ +defmodule Astarte.Test.Helpers.JWT do + alias Astarte.AppEngine.API.Auth.User + alias Astarte.AppEngine.APIWeb.AuthGuardian + + def public_key_pem do + Application.get_env(:astarte_appengine_api, :test_pub_key_pem) + end + + def gen_jwt_token(authorization_paths) do + jwk = + Application.get_env(:astarte_appengine_api, :test_priv_key) + |> JOSE.JWK.from_map() + + {:ok, jwt, claims} = + %User{id: "testuser"} + |> AuthGuardian.encode_and_sign( + %{a_aea: authorization_paths}, + secret: jwk, + allowed_algos: ["RS256"] + ) + + {jwt, claims} + end + + def gen_jwt_all_access_token do + gen_jwt_token([".*::.*"]) + end + + def gen_channels_jwt_token(authorization_paths) do + jwk = + Application.get_env(:astarte_appengine_api, :test_priv_key) + |> JOSE.JWK.from_map() + + {:ok, jwt, _claims} = + %User{id: "testuser"} + |> AuthGuardian.encode_and_sign( + %{a_ch: authorization_paths}, + secret: jwk, + allowed_algos: ["RS256"] + ) + + jwt + end + + def gen_channels_jwt_all_access_token do + gen_channels_jwt_token(["JOIN::.*", "WATCH::.*"]) + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/setups/conn.ex b/apps/astarte_appengine_api/test/support_v2/setups/conn.ex new file mode 100644 index 000000000..224378fa3 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/setups/conn.ex @@ -0,0 +1,22 @@ +defmodule Astarte.Test.Setups.Conn do + import Plug.Conn + alias Phoenix.ConnTest + alias Astarte.Test.Helpers.JWT, as: JWTHelper + + def create_conn(_context) do + {:ok, conn: ConnTest.build_conn()} + end + + def jwt(_context) do + {:ok, jwt: JWTHelper.gen_jwt_all_access_token()} + end + + def auth_conn(%{conn: conn, jwt: {jwt, _claims}}) do + auth_conn = + conn + |> put_req_header("accept", "application/json") + |> put_req_header("authorization", "bearer #{jwt}") + + {:ok, auth_conn: auth_conn} + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/setups/database.ex b/apps/astarte_appengine_api/test/support_v2/setups/database.ex new file mode 100644 index 000000000..ba0e9f839 --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/setups/database.ex @@ -0,0 +1,35 @@ +defmodule Astarte.Test.Setups.Database do + use ExUnit.Case, async: false + alias Astarte.Test.Helpers.Database, as: DatabaseHelper + alias Astarte.Test.Helpers.JWT, as: JWTHelper + alias Astarte.Test.Generators.String, as: StringGenerator + + # TODO + # doctest Astarte.Test.Fixture.Database + + def connect(_context) do + {:ok, cluster: :xandra} + end + + def realm(_context) do + {:ok, realm: StringGenerator.realm_name() |> Enum.at(0)} + end + + def setup(%{cluster: cluster, realm: realm}) do + on_exit(fn -> + DatabaseHelper.destroy_test_keyspace!(cluster, realm) + end) + + DatabaseHelper.create_test_keyspace!(cluster, realm) + {:ok, realm: realm} + end + + def setup_auth(%{cluster: cluster, realm: realm}) do + on_exit(fn -> + DatabaseHelper.delete!(:pubkeypem, cluster, realm) + end) + + DatabaseHelper.insert!(:pubkeypem, cluster, realm, JWTHelper.public_key_pem()) + {:ok, realm: realm} + end +end diff --git a/apps/astarte_appengine_api/test/support_v2/setups/interface.ex b/apps/astarte_appengine_api/test/support_v2/setups/interface.ex new file mode 100644 index 000000000..de5dbd01e --- /dev/null +++ b/apps/astarte_appengine_api/test/support_v2/setups/interface.ex @@ -0,0 +1,18 @@ +defmodule Astarte.Test.Setups.Interface do + use ExUnit.Case, async: false + alias Astarte.Test.Helpers.Database, as: DatabaseHelper + alias Astarte.Test.Generators.Interface, as: InterfaceGenerator + + def init(%{interface_count: interface_count}) do + {:ok, interfaces: InterfaceGenerator.interface() |> Enum.take(interface_count)} + end + + def setup(%{cluster: cluster, realm: realm, interfaces: interfaces}) do + on_exit(fn -> + DatabaseHelper.delete!(:interface, cluster, realm, interfaces) + end) + + DatabaseHelper.insert!(:interface, cluster, realm, interfaces) + :ok + end +end