From 3d289448cd87f41843f0a3e8f3966d10e1910259 Mon Sep 17 00:00:00 2001 From: Alexandre de Souza Date: Mon, 29 Apr 2024 11:06:57 -0300 Subject: [PATCH] Improve integration tests infrastructure (#2583) --- test/livebook_teams/hubs/team_client_test.exs | 183 +++----------- test/livebook_teams/hubs/team_test.exs | 30 --- test/livebook_teams/hubs_test.exs | 203 ++++++++++------ test/livebook_teams/teams/connection_test.exs | 226 ------------------ test/livebook_teams/teams_test.exs | 73 ++++-- 5 files changed, 218 insertions(+), 497 deletions(-) delete mode 100644 test/livebook_teams/hubs/team_test.exs delete mode 100644 test/livebook_teams/teams/connection_test.exs diff --git a/test/livebook_teams/hubs/team_client_test.exs b/test/livebook_teams/hubs/team_client_test.exs index 14249132b..3530a4def 100644 --- a/test/livebook_teams/hubs/team_client_test.exs +++ b/test/livebook_teams/hubs/team_client_test.exs @@ -6,168 +6,37 @@ defmodule Livebook.Hubs.TeamClientTest do setup do Livebook.Hubs.Broadcasts.subscribe([:connection, :file_systems, :secrets]) Livebook.Teams.Broadcasts.subscribe([:clients, :deployment_groups, :app_deployments, :agents]) + :ok end - test "rejects the web socket connection with invalid credentials", %{user: user, token: token} do - team = - build(:team, - user_id: user.id, - org_id: 123_456, - org_key_id: 123_456, - session_token: token - ) - - id = team.id - - TeamClient.start_link(team) - - assert_receive {:hub_server_error, ^id, error} - - assert error == - "#{team.hub_name}: Your session is out-of-date. Please re-join the organization." - - refute Livebook.Hubs.hub_exists?(team.id) - end - - describe "handle events" do - setup %{user: user, node: node} do - team = create_team_hub(user, node) + describe "connect" do + test "successfully authenticates the websocket connection", %{user: user, node: node} do + team = build_team_hub(user, node) id = team.id + TeamClient.start_link(team) assert_receive {:hub_connected, ^id} assert_receive {:client_connected, ^id} - - {:ok, team: team} end - test "receives secret events", %{team: team} do - secret = build(:secret, name: "SECRET", value: "BAR") - assert Livebook.Hubs.create_secret(team, secret) == :ok - - name = secret.name - value = secret.value - - # receives `{:secret_created, secret}` event - # with the value decrypted - assert_receive {:secret_created, %{name: ^name, value: ^value}} - - # updates the secret - updated_secret = Map.replace!(secret, :value, "BAZ") - assert Livebook.Hubs.update_secret(team, updated_secret) == :ok - - new_value = updated_secret.value - - # receives `{:secret_updated, secret}` event - # with the value decrypted - assert_receive {:secret_updated, %{name: ^name, value: ^new_value}} - - # deletes the secret - assert Livebook.Hubs.delete_secret(team, updated_secret) == :ok - - # receives `{:secret_deleted, secret}` event - assert_receive {:secret_deleted, %{name: ^name, value: ^new_value}} - end - - test "receives file system events", %{team: team} do - file_system = - build(:fs_s3, bucket_url: "https://file_system.s3.amazonaws.com", region: "us-east-1") - - assert Livebook.Hubs.create_file_system(team, file_system) == :ok - - bucket_url = file_system.bucket_url - region = file_system.region - - # receives `{:file_system_created, file_system}` event - assert_receive {:file_system_created, - %{external_id: id, bucket_url: ^bucket_url, region: ^region}} - - # updates the file system - updated_file_system = %{file_system | region: "eu-central-1", external_id: id} - assert Livebook.Hubs.update_file_system(team, updated_file_system) == :ok - - new_region = updated_file_system.region - - # receives `{:file_system_updated, file_system}` event - assert_receive {:file_system_updated, - %{external_id: ^id, bucket_url: ^bucket_url, region: ^new_region}} - - # deletes the file system - assert Livebook.Hubs.delete_file_system(team, updated_file_system) == :ok - - # receives `{:file_system_deleted, file_system}` event - assert_receive {:file_system_deleted, %{external_id: ^id, bucket_url: ^bucket_url}} - end - - test "receives deployment group events", %{team: team} do - deployment_group = - build(:deployment_group, name: "DEPLOYMENT_GROUP_#{team.id}", mode: :online) - - assert {:ok, _} = Livebook.Teams.create_deployment_group(team, deployment_group) - %{name: name, mode: mode} = deployment_group - - # receives `{:event, :deployment_group_created, deployment_group}` event - assert_receive {:deployment_group_created, %{name: ^name, mode: ^mode}} - end - - @tag :tmp_dir - test "receives app events", %{team: team, node: node, tmp_dir: tmp_dir} do - deployment_group = build(:deployment_group, name: team.id, mode: :online) - assert {:ok, id} = Livebook.Teams.create_deployment_group(team, deployment_group) - - id = to_string(id) - - assert_receive {:deployment_group_created, %{id: ^id}} - - # creates the app deployment - slug = Livebook.Utils.random_short_id() - title = "MyNotebook-#{slug}" - app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug} - - notebook = %{ - Livebook.Notebook.new() - | app_settings: app_settings, - name: title, - hub_id: team.id, - deployment_group_id: id - } - - files_dir = Livebook.FileSystem.File.local(tmp_dir) - {:ok, app_deployment} = Livebook.Teams.AppDeployment.new(notebook, files_dir) - :ok = Livebook.Teams.deploy_app(team, app_deployment) - - sha = app_deployment.sha - multi_session = app_settings.multi_session - access_type = app_settings.access_type - - assert_receive {:app_deployment_started, - %Livebook.Teams.AppDeployment{ - slug: ^slug, - sha: ^sha, - title: ^title, - multi_session: ^multi_session, - access_type: ^access_type, - deployment_group_id: ^id - } = app_deployment} - - # force app deployment to be deleted - erpc_call(node, :toggle_app_deployment, [app_deployment.id, team.org_id]) - - assert_receive {:app_deployment_stopped, ^app_deployment} - end - - test "receives the user events", %{team: team, node: node} do - Livebook.Hubs.Broadcasts.subscribe([:crud]) - - # force user to be deleted from org - erpc_call(node, :delete_user_org, [team.user_id, team.org_id]) + test "rejects the web socket connection with invalid credentials", %{user: user, token: token} do + team = + build(:team, + user_id: user.id, + org_id: 123_456, + org_key_id: 123_456, + session_token: token + ) id = team.id - reason = "#{team.hub_name}: you were removed from the org" - assert_receive {:hub_server_error, ^id, ^reason} - assert_receive {:hub_deleted, ^id} - refute team in Livebook.Hubs.get_hubs() + start_supervised!({TeamClient, team}) + + assert_receive {:hub_server_error, ^id, error} + + assert error == + "#{team.hub_name}: Your session is out-of-date. Please re-join the organization." end end @@ -187,6 +56,20 @@ defmodule Livebook.Hubs.TeamClientTest do {:ok, team: team, user_connected: user_connected} end + test "receives the user events", %{team: team, node: node} do + Livebook.Hubs.Broadcasts.subscribe([:crud]) + + # force user to be deleted from org + erpc_call(node, :delete_user_org, [team.user_id, team.org_id]) + + id = team.id + reason = "#{team.hub_name}: you were removed from the org" + + assert_receive {:hub_server_error, ^id, ^reason} + assert_receive {:hub_deleted, ^id} + refute team in Livebook.Hubs.get_hubs() + end + test "dispatches the secrets list", %{team: team, user_connected: user_connected} do secret = build(:secret, diff --git a/test/livebook_teams/hubs/team_test.exs b/test/livebook_teams/hubs/team_test.exs deleted file mode 100644 index 86c9fb1a5..000000000 --- a/test/livebook_teams/hubs/team_test.exs +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Livebook.Hubs.TeamTest do - use Livebook.TeamsIntegrationCase, async: true - - alias Livebook.Hubs.Provider - - describe "stamping" do - test "generates and verifies stamp for a notebook", %{user: user, node: node} do - team = create_team_hub(user, node) - - notebook_source = """ - # Team notebook - - # Intro - - ```elixir - IO.puts("Hello!") - ``` - """ - - metadata = %{"key" => "value"} - - assert {:ok, stamp} = Provider.notebook_stamp(team, notebook_source, metadata) - - assert {:ok, ^metadata} = Provider.verify_notebook_stamp(team, notebook_source, stamp) - - assert {:error, :invalid} = - Provider.verify_notebook_stamp(team, notebook_source <> "change\n", stamp) - end - end -end diff --git a/test/livebook_teams/hubs_test.exs b/test/livebook_teams/hubs_test.exs index fd73e0c1c..034e99bc8 100644 --- a/test/livebook_teams/hubs_test.exs +++ b/test/livebook_teams/hubs_test.exs @@ -3,8 +3,15 @@ defmodule Livebook.HubsTest do alias Livebook.Hubs + setup do + Livebook.Hubs.Broadcasts.subscribe([:connection, :file_systems, :secrets]) + Livebook.Teams.Broadcasts.subscribe([:clients]) + + :ok + end + test "get_hubs/0 returns a list of persisted hubs", %{user: user, node: node} do - team = create_team_hub(user, node) + team = connect_to_teams(user, node) assert team in Hubs.get_hubs() Hubs.delete_hub(team.id) @@ -12,7 +19,7 @@ defmodule Livebook.HubsTest do end test "get_metadata/0 returns a list of persisted hubs normalized", %{user: user, node: node} do - team = create_team_hub(user, node) + team = connect_to_teams(user, node) metadata = Hubs.Provider.to_metadata(team) assert metadata in Hubs.get_metadata() @@ -28,7 +35,7 @@ defmodule Livebook.HubsTest do Hubs.fetch_hub!("nonexistent") end - team = create_team_hub(user, node) + team = connect_to_teams(user, node) assert Hubs.fetch_hub!(team.id) == team end @@ -49,7 +56,7 @@ defmodule Livebook.HubsTest do end test "updates hub", %{user: user, node: node} do - team = create_team_hub(user, node) + team = connect_to_teams(user, node) Hubs.save_hub(%{team | hub_emoji: "🐈"}) assert Hubs.fetch_hub!(team.id).hub_emoji == "🐈" @@ -58,10 +65,13 @@ defmodule Livebook.HubsTest do describe "create_secret/2" do test "creates a new secret", %{user: user, node: node} do - hub = create_team_hub(user, node) - secret = build(:secret, name: "FOO", value: "BAR") + hub = connect_to_teams(user, node) + name = secret_name(hub) + value = hub.id + secret = build(:secret, name: name, value: value, hub_id: hub.id) assert Hubs.create_secret(hub, secret) == :ok + assert secret in Hubs.get_secrets(hub) # Guarantee uniqueness assert {:error, changeset} = Hubs.create_secret(hub, secret) @@ -69,69 +79,83 @@ defmodule Livebook.HubsTest do end test "returns changeset errors when data is invalid", %{user: user, node: node} do - hub = create_team_hub(user, node) - secret = build(:secret, name: "LB_FOO", value: "BAR") + hub = connect_to_teams(user, node) + secret = build(:secret, name: "LB_FOO", value: "BAR", hub_id: hub.id) assert {:error, changeset} = Hubs.create_secret(hub, secret) assert "cannot start with the LB_ prefix" in errors_on(changeset).name + refute secret in Hubs.get_secrets(hub) end end describe "update_secret/2" do test "updates a secret", %{user: user, node: node} do - hub = create_team_hub(user, node) - secret = build(:secret, name: "UPDATE_ME", value: "BAR") + hub = connect_to_teams(user, node) + name = secret_name(hub) + value = hub.id + secret = build(:secret, name: name, value: value, hub_id: hub.id) assert Hubs.create_secret(hub, secret) == :ok + assert_receive {:secret_created, %{name: ^name, value: ^value}} + assert secret in Hubs.get_secrets(hub) - update_secret = Map.replace!(secret, :value, "BAZ") - assert Hubs.update_secret(hub, update_secret) == :ok + new_value = "BAZ" + updated_secret = Map.replace!(secret, :value, new_value) + + assert Hubs.update_secret(hub, updated_secret) == :ok + assert_receive {:secret_updated, %{name: ^name, value: ^new_value}} + refute secret in Hubs.get_secrets(hub) + assert updated_secret in Hubs.get_secrets(hub) end test "returns changeset errors when data is invalid", %{user: user, node: node} do - hub = create_team_hub(user, node) - secret = build(:secret, name: "FIX_ME", value: "BAR") + hub = connect_to_teams(user, node) + name = secret_name(hub) + value = hub.id + secret = build(:secret, name: name, value: value, hub_id: hub.id) assert Hubs.create_secret(hub, secret) == :ok + assert_receive {:secret_created, %{name: ^name, value: ^value}} - update_secret = Map.replace!(secret, :value, "") + updated_secret = Map.replace!(secret, :value, "") - assert {:error, changeset} = Hubs.update_secret(hub, update_secret) + assert {:error, changeset} = Hubs.update_secret(hub, updated_secret) assert "can't be blank" in errors_on(changeset).value end end - describe "delete_secret/2" do - test "deletes a secret", %{user: user, node: node} do - hub = create_team_hub(user, node) - secret = build(:secret, name: "DELETE_ME", value: "BAR") + test "delete_secret/2 deletes a secret", %{user: user, node: node} do + hub = connect_to_teams(user, node) + name = secret_name(hub) + value = hub.id + secret = build(:secret, name: name, value: value, hub_id: hub.id) - assert Hubs.create_secret(hub, secret) == :ok - assert Hubs.delete_secret(hub, secret) == :ok + assert Hubs.create_secret(hub, secret) == :ok + assert_receive {:secret_created, %{name: ^name, value: ^value}} + assert secret in Hubs.get_secrets(hub) - # Guarantee it's been removed and will return HTTP status 404 - assert Hubs.delete_secret(hub, secret) == - {:transport_error, - "Something went wrong, try again later or please file a bug if it persists"} - end + assert Hubs.delete_secret(hub, secret) == :ok + assert_receive {:secret_deleted, %{name: ^name, value: ^value}} + refute secret in Hubs.get_secrets(hub) - test "returns transport errors when secret doesn't exists", %{user: user, node: node} do - hub = create_team_hub(user, node) - secret = build(:secret, name: "I_CANT_EXIST", value: "BAR") + # Guarantee it's been removed and will return HTTP status 404 + assert Hubs.delete_secret(hub, secret) == + {:transport_error, + "Something went wrong, try again later or please file a bug if it persists"} - # Guarantee it doesn't exists and will return HTTP status 404 - assert Hubs.delete_secret(hub, secret) == - {:transport_error, - "Something went wrong, try again later or please file a bug if it persists"} - end + refute_receive {:secret_deleted, _} end describe "create_file_system/2" do test "creates a new file system", %{user: user, node: node} do - hub = create_team_hub(user, node) - file_system = build(:fs_s3, bucket_url: "https://file_system_created.s3.amazonaws.com") + hub = connect_to_teams(user, node) + + bucket_url = "https://#{hub.id}.s3.amazonaws.com" + file_system = build(:fs_s3, bucket_url: bucket_url) + region = file_system.region assert Hubs.create_file_system(hub, file_system) == :ok + assert_receive {:file_system_created, %{bucket_url: ^bucket_url, region: ^region}} # Guarantee uniqueness assert {:error, changeset} = Hubs.create_file_system(hub, file_system) @@ -139,32 +163,35 @@ defmodule Livebook.HubsTest do end test "returns changeset errors when data is invalid", %{user: user, node: node} do - hub = create_team_hub(user, node) + hub = connect_to_teams(user, node) file_system = build(:fs_s3, bucket_url: nil) assert {:error, changeset} = Hubs.create_file_system(hub, file_system) assert "can't be blank" in errors_on(changeset).bucket_url + refute_receive {:file_system_created, _} end end describe "update_file_system/2" do test "updates a file system", %{user: user, node: node} do - hub = create_team_hub(user, node) - teams_file_system = create_teams_file_system(hub, node) + hub = connect_to_teams(user, node) + bucket_url = "https://#{hub.id}.s3.amazonaws.com" + file_system = build(:fs_s3, bucket_url: bucket_url) - file_system = - build(:fs_s3, - bucket_url: teams_file_system.name, - region: "us-east-1", - external_id: to_string(teams_file_system.id) - ) + assert Hubs.create_file_system(hub, file_system) == :ok + assert_receive {:file_system_created, %{external_id: external_id} = file_system} + assert file_system in Hubs.get_file_systems(hub) - update_file_system = Map.replace!(file_system, :region, "eu-central-1") - assert Hubs.update_file_system(hub, update_file_system) == :ok + updated_file_system = Map.replace!(file_system, :region, "eu-central-1") + + assert Hubs.update_file_system(hub, updated_file_system) == :ok + assert_receive {:file_system_updated, %{external_id: ^external_id, region: "eu-central-1"}} + refute file_system in Hubs.get_file_systems(hub) + assert updated_file_system in Hubs.get_file_systems(hub) end test "returns changeset errors when data is invalid", %{user: user, node: node} do - hub = create_team_hub(user, node) + hub = connect_to_teams(user, node) teams_file_system = create_teams_file_system(hub, node) file_system = @@ -177,42 +204,64 @@ defmodule Livebook.HubsTest do assert {:error, changeset} = Hubs.update_file_system(hub, update_file_system) assert "can't be blank" in errors_on(changeset).bucket_url + refute_receive {:file_system_updated, _} end end - describe "delete_file_system/2" do - test "deletes a file system", %{user: user, node: node} do - hub = create_team_hub(user, node) - teams_file_system = create_teams_file_system(hub, node) + test "delete_file_system/2 deletes a file system", %{user: user, node: node} do + hub = connect_to_teams(user, node) + bucket_url = "https://#{hub.id}.s3.amazonaws.com" + file_system = build(:fs_s3, bucket_url: bucket_url) - file_system = - build(:fs_s3, - bucket_url: teams_file_system.name, - region: "us-east-1", - external_id: to_string(teams_file_system.id) - ) + assert Hubs.create_file_system(hub, file_system) == :ok + assert_receive {:file_system_created, %{external_id: external_id} = file_system} + assert file_system in Hubs.get_file_systems(hub) - assert Hubs.delete_file_system(hub, file_system) == :ok + assert Hubs.delete_file_system(hub, file_system) == :ok + assert_receive {:file_system_deleted, %{external_id: ^external_id}} + refute file_system in Hubs.get_file_systems(hub) - # Guarantee it's been removed and will return HTTP status 404 - assert Hubs.delete_file_system(hub, file_system) == - {:transport_error, - "Something went wrong, try again later or please file a bug if it persists"} - end + # Guarantee it's been removed and will return HTTP status 404 + assert Hubs.delete_file_system(hub, file_system) == + {:transport_error, + "Something went wrong, try again later or please file a bug if it persists"} - test "returns transport errors when file system doesn't exists", %{user: user, node: node} do - hub = create_team_hub(user, node) + refute_receive {:file_system_deleted, _} + end - file_system = - build(:fs_s3, - bucket_url: "https://i_cant_exist.s3.amazonaws.com", - external_id: "123456789" - ) + test "generates and verifies stamp for a notebook", %{user: user, node: node} do + team = connect_to_teams(user, node) - # Guarantee it doesn't exists and will return HTTP status 404 - assert Hubs.delete_file_system(hub, file_system) == - {:transport_error, - "Something went wrong, try again later or please file a bug if it persists"} - end + notebook_source = """ + # Team notebook + + # Intro + + ```elixir + IO.puts("Hello!") + ``` + """ + + metadata = %{"key" => "value"} + + assert {:ok, stamp} = Hubs.Provider.notebook_stamp(team, notebook_source, metadata) + assert {:ok, ^metadata} = Hubs.Provider.verify_notebook_stamp(team, notebook_source, stamp) + + assert {:error, :invalid} = + Hubs.Provider.verify_notebook_stamp(team, notebook_source <> "change\n", stamp) + end + + defp connect_to_teams(user, node) do + %{id: id} = team = create_team_hub(user, node) + assert_receive {:hub_connected, ^id} + assert_receive {:client_connected, ^id} + + team + end + + defp secret_name(%{id: id}) do + id + |> String.replace("-", "_") + |> String.upcase() end end diff --git a/test/livebook_teams/teams/connection_test.exs b/test/livebook_teams/teams/connection_test.exs deleted file mode 100644 index 8ee1058a6..000000000 --- a/test/livebook_teams/teams/connection_test.exs +++ /dev/null @@ -1,226 +0,0 @@ -defmodule Livebook.Teams.ConnectionTest do - alias Livebook.FileSystem - use Livebook.TeamsIntegrationCase, async: true - - alias Livebook.Teams.Connection - - describe "connect" do - test "successfully authenticates the websocket connection", %{user: user, node: node} do - {_, headers} = build_team_headers(user, node) - - assert {:ok, _conn} = Connection.start_link(self(), headers) - assert_receive :connected - end - - test "rejects the websocket connection with invalid credentials", %{user: user} do - headers = [ - {"x-user", to_string(user.id)}, - {"x-org", to_string(user.id)}, - {"x-org-key", to_string(user.id)}, - {"x-session-token", "foo"} - ] - - assert {:ok, _conn} = Connection.start_link(self(), headers) - - assert_receive {:server_error, - "Your session is out-of-date. Please re-join the organization."} - - assert {:ok, _conn} = Connection.start_link(self(), []) - - assert_receive {:server_error, - "Invalid request. Please re-join the organization and update Livebook if the issue persists."} - end - end - - describe "handle events" do - test "receives the secret_created event", %{user: user, node: node} do - {hub, headers} = build_team_headers(user, node) - - assert {:ok, _conn} = Connection.start_link(self(), headers) - assert_receive :connected - - # creates a new secret - secret = build(:secret, name: "FOO", value: "BAR") - assert Livebook.Hubs.create_secret(hub, secret) == :ok - - # receives `{:event, :secret_created, secret_created}` event - # without decrypting the value - assert_receive {:event, :secret_created, secret_created} - assert secret_created.name == secret.name - refute secret_created.value == secret.value - end - - test "receives the file_system_created event", %{user: user, node: node} do - {hub, headers} = build_team_headers(user, node) - - assert {:ok, _conn} = Connection.start_link(self(), headers) - assert_receive :connected - - # creates a new file system - file_system = build(:fs_s3, bucket_url: "https://file_system_created.s3.amazonaws.com") - assert Livebook.Hubs.create_file_system(hub, file_system) == :ok - type = Livebook.FileSystems.type(file_system) - %{name: name} = FileSystem.external_metadata(file_system) - - # receives `{:event, :file_system_created, file_system_created}` event - # without decrypting the value - assert_receive {:event, :file_system_created, file_system_created} - assert file_system_created.name == name - assert file_system_created.type == to_string(type) - refute file_system_created.value == FileSystem.dump(file_system) - assert is_binary(file_system_created.value) - end - - test "receives the deployment_group_created event", %{user: user, node: node} do - {hub, headers} = build_team_headers(user, node) - - assert {:ok, _conn} = Connection.start_link(self(), headers) - assert_receive :connected - - # creates a new deployment group with offline mode - deployment_group = build(:deployment_group, name: "FOO", mode: :offline, clustering: :dns) - - assert {:ok, _id} = - Livebook.Teams.create_deployment_group(hub, deployment_group) - - # deployment_group name and mode are not encrypted - assert_receive {:event, :deployment_group_created, deployment_group_created} - assert deployment_group_created.name == deployment_group.name - assert String.to_existing_atom(deployment_group_created.mode) == deployment_group.mode - - assert String.to_existing_atom(deployment_group_created.clustering) == - deployment_group.clustering - - # since the deployment group is with offline mode, the agent key shouldn't exists - assert deployment_group_created.agent_keys == [] - - # creates a new deployment group with online mode - deployment_group = build(:deployment_group, name: "BAR", mode: :online, clustering: :dns) - {:ok, _id} = Livebook.Teams.create_deployment_group(hub, deployment_group) - - # deployment_group name and mode are not encrypted - assert_receive {:event, :deployment_group_created, deployment_group_created} - assert deployment_group_created.name == deployment_group.name - assert String.to_existing_atom(deployment_group_created.mode) == deployment_group.mode - - assert String.to_existing_atom(deployment_group_created.clustering) == - deployment_group.clustering - - # receives the built-in agent key - assert [agent_key] = deployment_group_created.agent_keys - assert is_binary(agent_key.key) - assert agent_key.deployment_group_id == deployment_group_created.id - end - - @tag :tmp_dir - test "receives the app deployments list from user_connected event", - %{user: user, node: node, tmp_dir: tmp_dir} do - {hub, headers} = build_team_headers(user, node) - - # creates a new deployment group - deployment_group = build(:deployment_group, name: "BAZ", mode: :online) - {:ok, id} = Livebook.Teams.create_deployment_group(hub, deployment_group) - - # creates a new app deployment - deployment_group_id = to_string(id) - slug = Livebook.Utils.random_short_id() - title = "MyNotebook3-#{slug}" - app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug} - - notebook = %{ - Livebook.Notebook.new() - | app_settings: app_settings, - name: title, - hub_id: hub.id, - deployment_group_id: deployment_group_id - } - - files_dir = Livebook.FileSystem.File.local(tmp_dir) - - {:ok, %Livebook.Teams.AppDeployment{} = app_deployment} = - Livebook.Teams.AppDeployment.new(notebook, files_dir) - - # since we want to fetch the app deployment from connection event, - # we need to persist it before we connect to the WebSocket - :ok = Livebook.Teams.deploy_app(hub, app_deployment) - - assert {:ok, _conn} = Connection.start_link(self(), headers) - assert_receive :connected - - assert_receive {:event, :user_connected, user_connected} - assert [app_deployment2] = user_connected.app_deployments - assert app_deployment2.title == title - assert app_deployment2.slug == slug - assert app_deployment2.sha == app_deployment.sha - assert app_deployment2.deployment_group_id == deployment_group_id - end - - @tag :tmp_dir - test "receives the app deployments list from agent_connected event", - %{user: user, node: node, tmp_dir: tmp_dir} do - # To create a new app deployment, we need use the User connection - {hub, _headers} = build_team_headers(user, node) - - # creates a new deployment group - deployment_group = build(:deployment_group, name: "BAZ", mode: :online) - {:ok, id} = Livebook.Teams.create_deployment_group(hub, deployment_group) - teams_deployment_group = erpc_call(node, :get_deployment_group!, [id]) - [teams_agent_key] = teams_deployment_group.agent_keys - - # creates a new app deployment - slug = Livebook.Utils.random_short_id() - title = "MyNotebook3-#{slug}" - app_settings = %{Livebook.Notebook.AppSettings.new() | slug: slug} - - notebook = %{ - Livebook.Notebook.new() - | app_settings: app_settings, - name: title, - hub_id: hub.id, - deployment_group_id: to_string(id) - } - - files_dir = Livebook.FileSystem.File.local(tmp_dir) - - {:ok, %Livebook.Teams.AppDeployment{} = app_deployment} = - Livebook.Teams.AppDeployment.new(notebook, files_dir) - - # since we want to fetch the app deployment from connection event, - # we need to persist it before we connect to the WebSocket - :ok = Livebook.Teams.deploy_app(hub, app_deployment) - - # As we need to be Agent to receive the app deployments list to be deployed, - # we will create another connection here - public_key = hub.org_public_key - - hub = %{ - hub - | user_id: nil, - org_public_key: nil, - session_token: teams_agent_key.key - } - - agent_name = Livebook.Config.agent_name() - - headers = [ - {"x-lb-version", Livebook.Config.app_version()}, - {"x-org", to_string(hub.org_id)}, - {"x-org-key", to_string(hub.org_key_id)}, - {"x-agent-name", agent_name}, - {"x-agent-key", hub.session_token} - ] - - assert {:ok, _conn} = Connection.start_link(self(), headers) - assert_receive :connected - - assert_receive {:event, :agent_connected, agent_connected} - assert agent_connected.name == agent_name - assert agent_connected.public_key == public_key - assert [app_deployment2] = agent_connected.app_deployments - assert app_deployment2.title == title - assert app_deployment2.slug == slug - assert app_deployment2.sha == app_deployment.sha - assert app_deployment2.deployment_group_id == to_string(id) - end - end -end diff --git a/test/livebook_teams/teams_test.exs b/test/livebook_teams/teams_test.exs index a66b84513..c7a1e22f0 100644 --- a/test/livebook_teams/teams_test.exs +++ b/test/livebook_teams/teams_test.exs @@ -1,9 +1,16 @@ defmodule Livebook.TeamsTest do use Livebook.TeamsIntegrationCase, async: true - alias Livebook.{Notebook, Teams, Utils} + alias Livebook.{FileSystem, Notebook, Teams, Utils} alias Livebook.Teams.Org + setup do + Livebook.Hubs.Broadcasts.subscribe([:connection, :file_systems, :secrets]) + Livebook.Teams.Broadcasts.subscribe([:clients, :deployment_groups, :app_deployments, :agents]) + + :ok + end + describe "create_org/1" do test "returns the device flow data to confirm the org creation" do org = build(:org) @@ -161,10 +168,17 @@ defmodule Livebook.TeamsTest do describe "create_deployment_group/2" do test "creates a new deployment group when the data is valid", %{user: user, node: node} do - team = create_team_hub(user, node) - deployment_group = build(:deployment_group) + team = connect_to_teams(user, node) - assert {:ok, _id} = Teams.create_deployment_group(team, deployment_group) + deployment_group = + build(:deployment_group, name: "DEPLOYMENT_GROUP_#{team.id}", mode: :online) + + assert {:ok, id} = Teams.create_deployment_group(team, deployment_group) + + %{name: name, mode: mode} = deployment_group + id = to_string(id) + + assert_receive {:deployment_group_created, %{id: ^id, name: ^name, mode: ^mode}} # Guarantee uniqueness assert {:error, changeset} = Teams.create_deployment_group(team, deployment_group) @@ -172,50 +186,69 @@ defmodule Livebook.TeamsTest do end test "returns changeset errors when the name is invalid", %{user: user, node: node} do - team = create_team_hub(user, node) + team = connect_to_teams(user, node) deployment_group = %{build(:deployment_group) | name: ""} assert {:error, changeset} = Teams.create_deployment_group(team, deployment_group) assert "can't be blank" in errors_on(changeset).name + refute_receive {:deployment_group_created, _} end test "returns changeset errors when the mode is invalid", %{user: user, node: node} do - team = create_team_hub(user, node) + team = connect_to_teams(user, node) deployment_group = %{build(:deployment_group) | mode: "invalid"} assert {:error, changeset} = Teams.create_deployment_group(team, deployment_group) assert "is invalid" in errors_on(changeset).mode + refute_receive {:deployment_group_created, _} end end describe "deploy_app/2" do @tag :tmp_dir test "deploys app to Teams from a notebook", %{user: user, node: node, tmp_dir: tmp_dir} do - team = create_team_hub(user, node) + team = connect_to_teams(user, node) deployment_group = build(:deployment_group, name: "BAZ", mode: :online) - {:ok, id} = Teams.create_deployment_group(team, deployment_group) - app_settings = %{Notebook.AppSettings.new() | slug: Utils.random_short_id()} + id = to_string(id) + assert_receive {:deployment_group_created, %{id: ^id}} + + # creates the app deployment + slug = Utils.random_short_id() + title = "MyNotebook-#{slug}" + app_settings = %{Notebook.AppSettings.new() | slug: slug} notebook = %{ Notebook.new() | app_settings: app_settings, - name: "MyNotebook", + name: title, hub_id: team.id, - deployment_group_id: to_string(id) + deployment_group_id: id } - files_dir = Livebook.FileSystem.File.local(tmp_dir) - + files_dir = FileSystem.File.local(tmp_dir) assert {:ok, app_deployment} = Teams.AppDeployment.new(notebook, files_dir) assert Teams.deploy_app(team, app_deployment) == :ok + sha = app_deployment.sha + multi_session = app_settings.multi_session + access_type = app_settings.access_type + + assert_receive {:app_deployment_started, + %Livebook.Teams.AppDeployment{ + slug: ^slug, + sha: ^sha, + title: ^title, + multi_session: ^multi_session, + access_type: ^access_type, + deployment_group_id: ^id + } = app_deployment2} + assert {:error, %{errors: [slug: {"should only contain alphanumeric characters and dashes", []}]}} = Teams.deploy_app(team, %{app_deployment | slug: "@abc"}) - # Since the fields below belongs to AppSettings, we're mapping the errors to `:file` field. assert {:error, %{errors: [multi_session: {"can't be blank", []}]}} = Teams.deploy_app(team, %{app_deployment | multi_session: nil}) @@ -224,6 +257,18 @@ defmodule Livebook.TeamsTest do assert {:error, %{errors: [access_type: {"is invalid", []}]}} = Teams.deploy_app(team, %{app_deployment | access_type: :abc}) + + # force app deployment to be stopped + erpc_call(node, :toggle_app_deployment, [app_deployment2.id, team.org_id]) + assert_receive {:app_deployment_stopped, ^app_deployment2} end end + + defp connect_to_teams(user, node) do + %{id: id} = team = create_team_hub(user, node) + assert_receive {:hub_connected, ^id} + assert_receive {:client_connected, ^id} + + team + end end