From db277757688ec2efcecd094d3b90eb632f8c862c Mon Sep 17 00:00:00 2001 From: Emric Pichonnier Date: Fri, 25 Mar 2022 10:07:07 +0100 Subject: [PATCH] feat: Create lenra data service (#168) --- apps/lenra/lib/lenra/services/data_service.ex | 59 ++ apps/lenra/mix.exs | 5 +- .../lenra/services/data_services_test.exs | 516 ++++++++++++++++++ apps/lenra_web/mix.exs | 2 +- mix.lock | 2 +- 5 files changed, 580 insertions(+), 4 deletions(-) create mode 100644 apps/lenra/lib/lenra/services/data_service.ex create mode 100644 apps/lenra/test/lenra/services/data_services_test.exs diff --git a/apps/lenra/lib/lenra/services/data_service.ex b/apps/lenra/lib/lenra/services/data_service.ex new file mode 100644 index 00000000..4fc7a8f0 --- /dev/null +++ b/apps/lenra/lib/lenra/services/data_service.ex @@ -0,0 +1,59 @@ +defmodule Lenra.DataServices do + @moduledoc """ + The service that manages application data. + """ + import Ecto.Query, only: [from: 2] + + alias Lenra.Repo + alias ApplicationRunner.{Data, DataServices, Datastore, UserData} + + def create(environment_id, params) do + environment_id + |> DataServices.create(params) + |> Repo.transaction() + end + + def create_and_link(user_id, environment_id, params) do + environment_id + |> DataServices.create(params) + |> Ecto.Multi.run(:user_data, fn repo, %{inserted_data: %Data{} = data} -> + repo.insert(UserData.new(%{user_id: user_id, data_id: data.id})) + end) + |> Repo.transaction() + end + + def update(data_id, params) do + data_id + |> DataServices.update(params) + |> Repo.transaction() + end + + def delete(data_id) do + data_id + |> DataServices.delete() + |> Repo.transaction() + end + + def get_old_data(user_id, environment_id) do + Repo.one( + from(d in Data, + join: u in UserData, + on: d.id == u.data_id, + join: ds in Datastore, + on: ds.id == d.datastore_id, + where: u.user_id == ^user_id and ds.environment_id == ^environment_id and ds.name == "UserDatas", + select: d + ) + ) + end + + def upsert_data(user_id, environment_id, data) do + case get_old_data(user_id, environment_id) do + nil -> + create_and_link(user_id, environment_id, data) + + old_data_id -> + update(old_data_id.id, data) + end + end +end diff --git a/apps/lenra/mix.exs b/apps/lenra/mix.exs index e108c739..48a9c293 100644 --- a/apps/lenra/mix.exs +++ b/apps/lenra/mix.exs @@ -13,14 +13,15 @@ defmodule Lenra.MixProject do elixirc_paths: elixirc_paths(Mix.env()), start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], - deps: deps() + deps: deps(), + xref: [exclude: [ApplicationRunner]] ] end def application do [ mod: {Lenra.Application, []}, - extra_applications: [:logger, :runtime_tools, :guardian, :bamboo] + extra_applications: [:logger, :runtime_tools, :guardian, :bamboo, :application_runner] ] end diff --git a/apps/lenra/test/lenra/services/data_services_test.exs b/apps/lenra/test/lenra/services/data_services_test.exs new file mode 100644 index 00000000..bb1ed748 --- /dev/null +++ b/apps/lenra/test/lenra/services/data_services_test.exs @@ -0,0 +1,516 @@ +defmodule Lenra.DataServicesTest do + @moduledoc """ + Test the datastore services + """ + use Lenra.RepoCase, async: true + + import Ecto.Query, only: [from: 2] + + alias ApplicationRunner.{Data, DataReferences, Datastore, DatastoreServices, UserData} + alias Lenra.{DataServices, Environment, LenraApplication, LenraApplicationServices, Repo} + + setup do + {:ok, %{inserted_user: user}} = UserTestHelper.register_john_doe() + + LenraApplicationServices.create(user.id, %{ + name: "mine-sweeper", + color: "FFFFFF", + icon: "60189" + }) + + env = Repo.get_by(Environment, application_id: Enum.at(Repo.all(LenraApplication), 0).id) + {:ok, env_id: env.id, user_id: user.id} + end + + describe "DataServices.create_1/1" do + test "should create data if json valid", %{env_id: env_id, user_id: user_id} do + {:ok, inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{"datastore" => "users", "data" => %{"name" => "toto"}}) + + data = Repo.get(Data, inserted_data.id) + + assert data.datastore_id == inserted_datastore.id + assert data.data == %{"name" => "toto"} + end + + test "should return error if json invalid", %{env_id: env_id, user_id: user_id} do + Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + + assert {:error, :data, :json_format_invalid, _changes_so_far} = + DataServices.create(env_id, %{ + "datastore" => "users", + "test" => %{"name" => "toto"} + }) + end + + test "should return error if env_id invalid", %{env_id: env_id, user_id: user_id} do + Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + + assert {:error, :datastore, :datastore_not_found, _changes_so_far} = + DataServices.create(-1, %{ + "datastore" => "users", + "data" => %{"name" => "toto"} + }) + end + + test "should return error if datastore name invalid", %{env_id: env_id, user_id: user_id} do + assert {:error, :datastore, :datastore_not_found, _changes_so_far} = + DataServices.create(env_id, %{ + "datastore" => "test", + "data" => %{"name" => "toto"} + }) + end + + test "should create reference if refs id is valid", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_point}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "10"} + }) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refs" => [inserted_point.id] + }) + + assert !is_nil(Repo.get_by(DataReferences, refs_id: inserted_point.id, refBy_id: inserted_data.id)) + end + + test "should create 2 if give 2 refs_id", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_point}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "10"} + }) + + {:ok, %{inserted_data: inserted_point_bis}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "12"} + }) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refs" => [inserted_point.id, inserted_point_bis.id] + }) + + assert !is_nil(Repo.get_by(DataReferences, refs_id: inserted_point.id, refBy_id: inserted_data.id)) + + assert !is_nil( + Repo.get_by(DataReferences, + refs_id: inserted_point_bis.id, + refBy_id: inserted_data.id + ) + ) + end + + test "should create reference if refBy id is valid", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_user}} = + DataServices.create(env_id, %{"datastore" => "users", "data" => %{"name" => "toto"}}) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "10"}, + "refBy" => [inserted_user.id] + }) + + assert !is_nil(Repo.get_by(DataReferences, refs_id: inserted_data.id, refBy_id: inserted_user.id)) + end + + test "should create reference if refs and refBy id is valid", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "team"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_team}} = + DataServices.create(env_id, %{"datastore" => "team", "data" => %{"name" => "test"}}) + + {:ok, %{inserted_data: inserted_point}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"scrore" => "10"} + }) + + {:ok, %{inserted_data: inserted_user}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refs" => [inserted_point.id], + "refBy" => [inserted_team.id] + }) + + assert !is_nil(Repo.get_by(DataReferences, refs_id: inserted_user.id, refBy_id: inserted_team.id)) + + assert !is_nil(Repo.get_by(DataReferences, refs_id: inserted_point.id, refBy_id: inserted_user.id)) + end + + test "should return error if refs id invalid ", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + assert {:error, :"inserted_refs_-1", %{errors: [refs_id: {"does not exist", _constraint}]}, _change_so_far} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refs" => [-1] + }) + end + + test "should return error if refBy_id invalid", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + assert {:error, :"inserted_refBy_-1", %{errors: [refBy_id: {"does not exist", _constraint}]}, _change_so_far} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "10"}, + "refBy" => [-1] + }) + end + end + + describe "Lenra.DataServices.get_old_data_1/1" do + test "should return last data", %{env_id: env_id, user_id: user_id} do + env_id + |> DatastoreServices.create(%{"name" => "UserDatas"}) + |> Repo.transaction() + + DataServices.create_and_link(user_id, env_id, %{"datastore" => "UserDatas", "data" => %{"test" => "test"}}) + + assert %{"test" => "test"} = DataServices.get_old_data(user_id, env_id).data + end + end + + describe "DataServices.delete_1/1" do + test "should delete data if json valid", %{env_id: env_id, user_id: user_id} do + Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{"datastore" => "users", "data" => %{"name" => "toto"}}) + + data = Repo.get(Data, inserted_data.id) + + DataServices.delete(data.id) + + deleted_data = Repo.get(Data, inserted_data.id) + + assert deleted_data == nil + end + + test "should return error id invalid", %{env_id: _env_id, user_id: _user_id} do + assert {:error, :data, :data_not_found, _changes_so_far} = DataServices.delete(-1) + end + end + + describe "DataServices.update_1/1" do + test "should update data if json valid", %{env_id: env_id, user_id: user_id} do + Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{"datastore" => "users", "data" => %{"name" => "toto"}}) + + data = Repo.get(Data, inserted_data.id) + + DataServices.update(data.id, %{"data" => %{"name" => "test"}}) + + updated_data = Repo.get(Data, inserted_data.id) + + assert updated_data.data == %{"name" => "test"} + end + + test "should return error id invalid", %{env_id: _env_id} do + assert {:error, :data, :data_not_found, _changes_so_far} = DataServices.update(-1, %{"data" => %{}}) + end + + test "should also update refs on update", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_point}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "10"} + }) + + {:ok, %{inserted_data: inserted_point_bis}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "12"} + }) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refs" => [inserted_point.id] + }) + + {:ok, %{data: updated_data}} = + DataServices.update(inserted_data.id, %{ + "refs" => [inserted_point_bis.id] + }) + + data = + Data + |> Repo.get(updated_data.id) + |> Repo.preload(:refs) + + assert 1 == length(data.refs) + + assert List.first(data.refs).id == + inserted_point_bis.id + end + + test "should also update refBy on update", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_data}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"} + }) + + {:ok, %{inserted_data: inserted_data_bis}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "test"} + }) + + {:ok, %{inserted_data: inserted_point}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"score" => "10"}, + "refBy" => [inserted_data.id] + }) + + {:ok, %{data: updated_data}} = + DataServices.update(inserted_point.id, %{ + "refBy" => [inserted_data_bis.id] + }) + + data = + Data + |> Repo.get(updated_data.id) + |> Repo.preload(:refBy) + + assert 1 == length(data.refBy) + + assert List.first(data.refBy).id == + inserted_data_bis.id + end + + test "should also update refs and refBy on update", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "team"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_team}} = + DataServices.create(env_id, %{ + "datastore" => "team", + "data" => %{"name" => "team1"} + }) + + {:ok, %{inserted_data: inserted_team_bis}} = + DataServices.create(env_id, %{ + "datastore" => "team", + "data" => %{"name" => "team2"} + }) + + {:ok, %{inserted_data: inserted_point}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"name" => "10"} + }) + + {:ok, %{inserted_data: inserted_point_bis}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"name" => "12"} + }) + + {:ok, %{inserted_data: inserted_user}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refs" => [inserted_point.id], + "refBy" => [inserted_team.id] + }) + + {:ok, %{data: updated_data}} = + DataServices.update(inserted_user.id, %{ + "refs" => [inserted_point_bis.id], + "refBy" => [inserted_team_bis.id] + }) + + data = + Data + |> Repo.get(updated_data.id) + |> Repo.preload(:refBy) + |> Repo.preload(:refs) + + assert 1 == length(data.refBy) + + assert List.first(data.refBy).id == + inserted_team_bis.id + + assert 1 == length(data.refs) + + assert List.first(data.refs).id == + inserted_point_bis.id + end + + test "should return error if update with invalid refs id", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "points"})) + + {:ok, %{inserted_data: inserted_point}} = + DataServices.create(env_id, %{ + "datastore" => "points", + "data" => %{"name" => "10"} + }) + + {:ok, %{inserted_data: inserted_user}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refs" => [inserted_point.id] + }) + + {:error, :refs, :references_not_found, _change_so_far} = + DataServices.update(inserted_user.id, %{ + "refs" => [-1] + }) + end + + test "should return error if update with invalid ref_by id", %{env_id: env_id, user_id: user_id} do + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "team"})) + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + + {:ok, %{inserted_data: inserted_team}} = + DataServices.create(env_id, %{ + "datastore" => "team", + "data" => %{"name" => "team1"} + }) + + {:ok, %{inserted_data: inserted_user}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refBy" => [inserted_team.id] + }) + + {:error, :refBy, :references_not_found, _change_so_far} = + DataServices.update(inserted_user.id, %{ + "refBy" => [-1] + }) + end + + test "should not update data if env_id not the same", %{env_id: env_id, user_id: user_id} do + {:ok, %{inserted_main_env: environment}} = + LenraApplicationServices.create(user_id, %{ + name: "test-update", + color: "FFFFFF", + icon: "60189" + }) + + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "team"})) + + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(environment.id, %{"name" => "team2"})) + + {:ok, _inserted_datastore} = Repo.insert(Datastore.new(env_id, %{"name" => "users"})) + + {:ok, %{inserted_data: inserted_team}} = + DataServices.create(env_id, %{ + "datastore" => "team", + "data" => %{"name" => "team1"} + }) + + {:ok, %{inserted_data: inserted_team_bis}} = + DataServices.create(environment.id, %{ + "datastore" => "team2", + "data" => %{"name" => "team2"} + }) + + {:ok, %{inserted_data: inserted_user}} = + DataServices.create(env_id, %{ + "datastore" => "users", + "data" => %{"name" => "toto"}, + "refBy" => [inserted_team.id] + }) + + {:error, :refBy, :references_not_found, _change_so_far} = + DataServices.update(inserted_user.id, %{ + "refBy" => [inserted_team_bis.id] + }) + end + end + + describe "Lenra.DataServices.upsert_data_1/1" do + test "should update last data if data exist", %{env_id: env_id, user_id: user_id} do + env_id + |> DatastoreServices.create(%{"name" => "UserDatas"}) + |> Repo.transaction() + + DataServices.create_and_link(user_id, env_id, %{"datastore" => "UserDatas", "data" => %{"test" => "test"}}) + + DataServices.upsert_data(user_id, env_id, %{"datastore" => "UserDatas", "data" => %{"test" => "test2"}}) + + assert %{"test" => "test2"} = DataServices.get_old_data(user_id, env_id).data + end + + test "should create data if data not exist", %{env_id: env_id, user_id: user_id} do + env_id + |> DatastoreServices.create(%{"name" => "UserDatas"}) + |> Repo.transaction() + + DataServices.upsert_data(user_id, env_id, %{"datastore" => "UserDatas", "data" => %{"test" => "test"}}) + + assert %{"test" => "test"} = DataServices.get_old_data(user_id, env_id).data + end + end + + describe "Lenra.DataServices.create_and_link_1/1" do + test "should create data and user_data", %{env_id: env_id, user_id: user_id} do + env_id + |> DatastoreServices.create(%{"name" => "UserDatas"}) + |> Repo.transaction() + + DataServices.create_and_link(user_id, env_id, %{"datastore" => "UserDatas", "data" => %{"test" => "test"}}) + + %{user_id: user_data_user_id, data_id: user_data_data_id} = + Repo.one( + from(u in UserData, + join: d in Data, + on: d.id == u.data_id, + join: ds in Datastore, + on: ds.id == d.datastore_id, + where: u.user_id == ^user_id and ds.environment_id == ^env_id and ds.name == "UserDatas", + select: u + ) + ) + + old_data = DataServices.get_old_data(user_id, env_id) + + assert %{"test" => "test"} = old_data.data + assert user_data_data_id == old_data.id + assert user_data_user_id == user_id + end + end +end diff --git a/apps/lenra_web/mix.exs b/apps/lenra_web/mix.exs index 5c043350..a7ddab5f 100644 --- a/apps/lenra_web/mix.exs +++ b/apps/lenra_web/mix.exs @@ -48,7 +48,7 @@ defmodule LenraWeb.MixProject do name: :application_runner, host: "github.com", project: "lenra-io/application-runner.git", - tag: "v1.0.0-data.2", + tag: "v1.0.0-data.1", credentials: "shiipou:#{System.get_env("GH_PERSONNAL_TOKEN")}" ) ] diff --git a/mix.lock b/mix.lock index 193f335a..002b2c05 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,5 @@ %{ - "application_runner": {:git, "git@github.com:lenra-io/application-runner.git", "81285f15c585078ca3c75ed5b661612d23de1d1f", [tag: "v1.0.0-data.2", submodules: true]}, + "application_runner": {:git, "git@github.com:lenra-io/application-runner.git", "38e44aeb5e895e32e61e08b234ee654bfa11a46a", [tag: "v1.0.0-data.1", submodules: true]}, "argon2_elixir": {:hex, :argon2_elixir, "2.4.0", "2a22ea06e979f524c53b42b598fc6ba38cdcbc977a155e33e057732cfb1fb311", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "4ea82e183cf8e7f66dab1f767fedcfe6a195e140357ef2b0423146b72e0a551d"}, "bamboo": {:hex, :bamboo, "2.1.0", "3c58f862efd74fa8c8d48a410ac592b41f7d24785e828566f7a0af549269ddc3", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f0ad2623b9a1d2dc06dcf289b59df9ebc522f49f3a21971ec87a8fce04e6d33e"}, "bamboo_smtp": {:hex, :bamboo_smtp, "4.0.1", "7e48188663f6164a81183688bb263be4c3952648fcd3ce52164f44d68777f9cd", [:mix], [{:bamboo, "~> 2.1.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 1.1.1", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "7ff1d62ae39bfb1c14f6d3cddba0fa1482a45c2a2b497a2da601eff7099605c8"},