From 7218ba3a932e96cb0e3b72bd94fa3e1655245dfa Mon Sep 17 00:00:00 2001 From: Nicholas Date: Wed, 20 Mar 2024 18:10:19 +0100 Subject: [PATCH] feature: add delete after duration column that stores the time to wait before deletion (#223) --- lib/qrstorage/qr_codes.ex | 8 ++- lib/qrstorage/qr_codes/qr_code.ex | 24 ++++++++- .../qr_codes/qr_code_migration_helper.ex | 19 +++++++ lib/qrstorage/services/qr_code_service.ex | 33 ++---------- lib/qrstorage/worker/remove_codes_worker.ex | 11 ++++ .../controllers/qr_code_controller.ex | 5 -- .../templates/qr_code/form_link.html.heex | 2 + .../settings/_settings_delete_after.html.heex | 24 ++++----- .../templates/qr_code/show.html.heex | 13 ++--- lib/qrstorage_web/views/qr_code_view.ex | 12 ++--- ...20240316123335_add_delete_after_months.exs | 17 ++++++ .../qr_code_migration_helper_test.exs | 45 ++++++++++++++++ test/qrstorage/qr_codes_test.exs | 37 ++----------- .../services/qr_code_service_test.exs | 53 +++++++++++++++++-- .../services/translation_service_test.exs | 20 +------ test/qrstorage/services/tts_service_test.exs | 20 +------ .../worker/remove_codes_worker_test.exs | 44 ++------------- .../controllers/qr_code_controller_test.exs | 34 ++---------- .../qrstorage_web/views/qr_code_view_test.exs | 10 +--- test/support/data_case.ex | 47 ++++++++++++++++ 20 files changed, 257 insertions(+), 221 deletions(-) create mode 100644 lib/qrstorage/qr_codes/qr_code_migration_helper.ex create mode 100644 priv/repo/migrations/20240316123335_add_delete_after_months.exs create mode 100644 test/qrstorage/qr_codes/qr_code_migration_helper_test.exs diff --git a/lib/qrstorage/qr_codes.ex b/lib/qrstorage/qr_codes.ex index aa4e786..2bb03fc 100644 --- a/lib/qrstorage/qr_codes.ex +++ b/lib/qrstorage/qr_codes.ex @@ -70,8 +70,12 @@ defmodule Qrstorage.QrCodes do :ok """ def delete_old_qr_codes() do - now = Timex.now() - Repo.delete_all(from q in QrCode, where: ^now > q.delete_after, select: [:id, :content_type]) + # delete all codes that are older than last access date + delete_after_months + Repo.delete_all( + from q in QrCode, + where: fragment("NOW() > ? + INTERVAL '1 month' * ?", q.last_accessed_at, q.delete_after_months), + select: [:id, :content_type] + ) end def delete_qr_code(%QrCode{} = qr_code) do diff --git a/lib/qrstorage/qr_codes/qr_code.ex b/lib/qrstorage/qr_codes/qr_code.ex index f5d2daf..ac6fabd 100644 --- a/lib/qrstorage/qr_codes/qr_code.ex +++ b/lib/qrstorage/qr_codes/qr_code.ex @@ -21,11 +21,13 @@ defmodule Qrstorage.QrCodes.QrCode do @text_length_limits %{link: 1500, audio: 2000, text: 4000, recording: 1} @max_delete_after_year 9999 + @valid_delete_after_months [0, 1, 6, 12, 36] @primary_key {:id, :binary_id, autogenerate: true} @foreign_key_type :binary_id schema "qrcodes" do field :delete_after, :date + field :delete_after_months, :integer, default: 1 field :text, :string field :translated_text, :string field :audio_file, :binary @@ -49,7 +51,7 @@ defmodule Qrstorage.QrCodes.QrCode do qr_code |> cast(attrs, [ :text, - :delete_after, + :delete_after_months, :color, :language, :hide_text, @@ -63,11 +65,13 @@ defmodule Qrstorage.QrCodes.QrCode do |> scrub_text |> validate_text_length(:text) |> validate_inclusion(:color, @colors) + |> validate_inclusion(:delete_after_months, @valid_delete_after_months) + |> validate_delete_after_months_by_type(:delete_after_months) |> validate_inclusion(:content_type, @content_types) |> validate_inclusion(:dots_type, @dots_types) |> validate_audio_type(:content_type) |> validate_link(:text) - |> validate_required([:text, :delete_after, :content_type, :dots_type]) + |> validate_required([:text, :delete_after_months, :content_type, :dots_type]) end def store_audio_file(qr_code, attrs) do @@ -158,6 +162,22 @@ defmodule Qrstorage.QrCodes.QrCode do end) end + def validate_delete_after_months_by_type(changeset, field) do + # We want to make sure that links and recordings are stored for 0 months and 1 month + validate_change(changeset, field, fn field, value -> + case get_field(changeset, :content_type) do + :recording -> + if value == 1, do: [], else: [{field, "Storage duration is invalid"}] + + :link -> + if value == 0, do: [], else: [{field, "Storage duration is invalid"}] + + _ -> + [] + end + end) + end + def translation_changed_text(qr_code) do qr_code.text != qr_code.translated_text end diff --git a/lib/qrstorage/qr_codes/qr_code_migration_helper.ex b/lib/qrstorage/qr_codes/qr_code_migration_helper.ex new file mode 100644 index 0000000..3683f8e --- /dev/null +++ b/lib/qrstorage/qr_codes/qr_code_migration_helper.ex @@ -0,0 +1,19 @@ +defmodule Qrstorage.QrCodes.QrCodeMigrationHelper do + @spec add_delete_after_months(Ecto.Repo.t()) :: any() + def add_delete_after_months(repo) do + repo.query!( + "UPDATE qrcodes +SET delete_after_months = + CASE + WHEN DATE_TRUNC('hour', delete_after) <= DATE_TRUNC('hour', inserted_at + INTERVAL '1 hour') THEN 0 + WHEN DATE_TRUNC('day', delete_after) <= DATE_TRUNC('day', inserted_at + INTERVAL '1 month') THEN 1 + WHEN DATE_TRUNC('day', delete_after) <= DATE_TRUNC('day', inserted_at + INTERVAL '6 months') THEN 6 + WHEN DATE_TRUNC('day', delete_after) <= DATE_TRUNC('day', inserted_at + INTERVAL '12 months') THEN 12 + ELSE 36 + END; +", + [], + log: :info + ) + end +end diff --git a/lib/qrstorage/services/qr_code_service.ex b/lib/qrstorage/services/qr_code_service.ex index 879ae04..293f7e7 100644 --- a/lib/qrstorage/services/qr_code_service.ex +++ b/lib/qrstorage/services/qr_code_service.ex @@ -11,11 +11,11 @@ defmodule Qrstorage.Services.QrCodeService do require Logger def create_qr_code(qr_code_params) do - qr_code_params = qr_code_params |> convert_delete_after() |> convert_deltas() + qr_code_params = qr_code_params |> convert_deltas() case QrCodes.create_qr_code(qr_code_params) do {:ok, %QrCode{} = qr_code} -> - case handle_audio_types(qr_code, qr_code_params) do + case handle_types(qr_code, qr_code_params) do {:error, error_message} -> # delete qr code and show error Logger.error("deleting qr code after creation, because the audio part could not be saved") @@ -31,33 +31,6 @@ defmodule Qrstorage.Services.QrCodeService do end end - defp convert_delete_after(qr_code_params) do - # delete links after one day. We only need them to display the qr code properly and for preview purposes. - delete_after = - case Map.get(qr_code_params, "content_type") do - "link" -> - Timex.shift(Timex.now(), hours: 1) - - "recording" -> - Timex.shift(Timex.now(), months: 1) - - _ -> - months = Map.get(qr_code_params, "delete_after") |> Integer.parse() |> elem(0) - - if months == 0 do - # Unfortunately, postgrex doesnt support postgres infinity type, - # so we have to fall back to date far away in the future: - # https://elixirforum.com/t/support-infinity-values-for-date-type/20713/17 - Timex.end_of_year(QrCode.max_delete_after_year()) - else - Timex.shift(Timex.now(), months: months) - end - end - - qr_code_params = Map.put(qr_code_params, "delete_after", delete_after) - qr_code_params - end - defp convert_deltas(qr_code_params) do # we need to convert the deltas to json: deltas_json = @@ -87,7 +60,7 @@ defmodule Qrstorage.Services.QrCodeService do Qrstorage.Services.TranslationService.add_translation(qr_code) end - defp handle_audio_types(qr_code, qr_code_params) do + defp handle_types(qr_code, qr_code_params) do case qr_code.content_type do :audio -> handle_audio_qr_code(qr_code) :recording -> handle_recording_qr_code(qr_code, qr_code_params) diff --git a/lib/qrstorage/worker/remove_codes_worker.ex b/lib/qrstorage/worker/remove_codes_worker.ex index 8181f99..9138865 100644 --- a/lib/qrstorage/worker/remove_codes_worker.ex +++ b/lib/qrstorage/worker/remove_codes_worker.ex @@ -12,6 +12,7 @@ defmodule Qrstorage.Worker.RemoveCodesWorker do delete_recordings(deleted_codes) delete_tts(deleted_codes) Logger.info("Finished deleting old qr codes: #{deleted_count}") + print_frequencies(deleted_codes) :ok end @@ -47,4 +48,14 @@ defmodule Qrstorage.Worker.RemoveCodesWorker do code.id end) end + + defp print_frequencies(deleted_codes) do + deleted_frequencies = + deleted_codes + |> Enum.frequencies_by(fn code -> + code.content_type + end) + + Logger.info("Codes deleted by type: #{inspect(deleted_frequencies)}") + end end diff --git a/lib/qrstorage_web/controllers/qr_code_controller.ex b/lib/qrstorage_web/controllers/qr_code_controller.ex index abe6117..493a846 100644 --- a/lib/qrstorage_web/controllers/qr_code_controller.ex +++ b/lib/qrstorage_web/controllers/qr_code_controller.ex @@ -39,11 +39,6 @@ defmodule QrstorageWeb.QrCodeController do def create(conn, %{"qr_code" => qr_code_params}) do case Qrstorage.Services.QrCodeService.create_qr_code(qr_code_params) do {:ok, qr_code} -> - conn = - if QrCode.stored_indefinitely?(qr_code), - do: put_flash(conn, :admin_url_id, qr_code.admin_url_id), - else: conn - conn |> redirect(to: Routes.qr_code_path(conn, :download, qr_code)) diff --git a/lib/qrstorage_web/templates/qr_code/form_link.html.heex b/lib/qrstorage_web/templates/qr_code/form_link.html.heex index bdd2a1f..fb43f2d 100644 --- a/lib/qrstorage_web/templates/qr_code/form_link.html.heex +++ b/lib/qrstorage_web/templates/qr_code/form_link.html.heex @@ -40,6 +40,8 @@ + <%= radio_button(f, :delete_after_months, 0, class: "visually-hidden", autocomplete: "off", checked: true) %> +
<%= submit(gettext("Save"), class: "btn btn-primary") %> diff --git a/lib/qrstorage_web/templates/qr_code/settings/_settings_delete_after.html.heex b/lib/qrstorage_web/templates/qr_code/settings/_settings_delete_after.html.heex index 274c36f..f4c0f26 100644 --- a/lib/qrstorage_web/templates/qr_code/settings/_settings_delete_after.html.heex +++ b/lib/qrstorage_web/templates/qr_code/settings/_settings_delete_after.html.heex @@ -1,23 +1,17 @@
<%= gettext("delete after") %>
+
+
+ <%= radio_button(@f, :delete_after_months, 1, class: "btn-check", autocomplete: "off") %> + -
- <%= radio_button(@f, :delete_after, 1, - class: "btn-check", - autocomplete: "off", - checked: true - ) %> - + <%= radio_button(@f, :delete_after_months, 12, class: "btn-check", autocomplete: "off", checked: true) %> + - <%= radio_button(@f, :delete_after, 6, class: "btn-check", autocomplete: "off") %> - - - <%= radio_button(@f, :delete_after, 12, class: "btn-check", autocomplete: "off") %> - - - <%= radio_button(@f, :delete_after, 0, class: "btn-check", autocomplete: "off") %> - + <%= radio_button(@f, :delete_after_months, 36, class: "btn-check", autocomplete: "off") %> + +
diff --git a/lib/qrstorage_web/templates/qr_code/show.html.heex b/lib/qrstorage_web/templates/qr_code/show.html.heex index d7ff05b..a2a13c8 100644 --- a/lib/qrstorage_web/templates/qr_code/show.html.heex +++ b/lib/qrstorage_web/templates/qr_code/show.html.heex @@ -42,20 +42,15 @@ <% end %>
+
+ <%= gettext("Deletion Date") %>
+ <%= show_delete_after_text(@qr_code) %> +
<%= if @qr_code.content_type == :audio do %> -
- <%= gettext("Deletion Date") %>
- <%= show_delete_after_text(@qr_code.delete_after) %> -
<%= gettext("Audio Language") %>
<%= Gettext.dgettext(QrstorageWeb.Gettext, "languages", Atom.to_string(@qr_code.language)) %>
- <% else %> -
- <%= gettext("Deletion Date") %>
- <%= show_delete_after_text(@qr_code.delete_after) %> -
<% end %>
diff --git a/lib/qrstorage_web/views/qr_code_view.ex b/lib/qrstorage_web/views/qr_code_view.ex index f3c1e30..e3efb72 100644 --- a/lib/qrstorage_web/views/qr_code_view.ex +++ b/lib/qrstorage_web/views/qr_code_view.ex @@ -42,12 +42,12 @@ defmodule QrstorageWeb.QrCodeView do end end - def show_delete_after_text(delete_after) do - if delete_after.year == QrCode.max_delete_after_year() do - gettext("This qr code will be stored indefinitely.") - else - Timex.format!(delete_after, "{relative}", :relative) - end + def show_delete_after_text(qr_code) do + Timex.format!(deletion_date(qr_code), "{relative}", :relative) + end + + def deletion_date(qr_code) do + Timex.shift(qr_code.last_accessed_at, months: qr_code.delete_after_months) end def dots_type_checked?(dots_type, changeset) do diff --git a/priv/repo/migrations/20240316123335_add_delete_after_months.exs b/priv/repo/migrations/20240316123335_add_delete_after_months.exs new file mode 100644 index 0000000..b96d1ed --- /dev/null +++ b/priv/repo/migrations/20240316123335_add_delete_after_months.exs @@ -0,0 +1,17 @@ +defmodule Qrstorage.Repo.Migrations.AddDeleteAfterMonths do + alias Qrstorage.QrCodes.QrCodeMigrationHelper + use Ecto.Migration + + def change do + alter table(:qrcodes) do + add :delete_after_months, :integer, default: 1 + end + + execute(&execute_up/0, &execute_down/0) + end + + defp execute_up, + do: QrCodeMigrationHelper.add_delete_after_months(repo()) + + defp execute_down, do: nil +end diff --git a/test/qrstorage/qr_codes/qr_code_migration_helper_test.exs b/test/qrstorage/qr_codes/qr_code_migration_helper_test.exs new file mode 100644 index 0000000..e152fff --- /dev/null +++ b/test/qrstorage/qr_codes/qr_code_migration_helper_test.exs @@ -0,0 +1,45 @@ +defmodule Qrstorage.QrCodes.QrCodeMigrationHelperTest do + use Qrstorage.DataCase + + alias Qrstorage.QrCodes.QrCodeMigrationHelper + alias Qrstorage.QrCodes.QrCode + + describe "add_deleter_after_months/1" do + test "calculates the delete after duration for qr_codes based on insert_at and delete_after date" do + Enum.each([1, 6, 12], fn month -> + qr_code = qr_code_fixture() + + # we need to manually tore the old delete_after date. the delete_after date can no longer be set, since we are deprecating it:application + updateDeleteAfterDate(qr_code, Timex.shift(Timex.now(), months: month)) + QrCodeMigrationHelper.add_delete_after_months(Qrstorage.Repo) + + qr_code = QrCodes.get_qr_code!(qr_code.id) + assert qr_code.delete_after_months == month + end) + end + + test "calculates the delete after duration for qr_codes based on insert_at and delete_after date for link codes" do + qr_code = qr_code_fixture() + updateDeleteAfterDate(qr_code, Timex.shift(Timex.now(), hours: 1)) + QrCodeMigrationHelper.add_delete_after_months(Qrstorage.Repo) + + qr_code = QrCodes.get_qr_code!(qr_code.id) + assert qr_code.delete_after_months == 0 + end + + test "calculates the delete after duration for qr_codes based on insert_at and delete_after date for infinity codes" do + qr_code = qr_code_fixture() + updateDeleteAfterDate(qr_code, Timex.end_of_year(QrCode.max_delete_after_year())) + QrCodeMigrationHelper.add_delete_after_months(Qrstorage.Repo) + + qr_code = QrCodes.get_qr_code!(qr_code.id) + assert qr_code.delete_after_months == 36 + end + end + + defp updateDeleteAfterDate(qr_code, date) do + qr_code + |> cast(%{delete_after: date}, [:delete_after]) + |> Repo.update!() + end +end diff --git a/test/qrstorage/qr_codes_test.exs b/test/qrstorage/qr_codes_test.exs index d1d7058..6c4a58a 100644 --- a/test/qrstorage/qr_codes_test.exs +++ b/test/qrstorage/qr_codes_test.exs @@ -7,7 +7,7 @@ defmodule Qrstorage.QrCodesTest do alias Qrstorage.QrCodes.QrCode @valid_attrs %{ - delete_after: ~D[2010-04-17], + delete_after_months: 1, text: "some text", hide_text: false, content_type: "text", @@ -18,7 +18,6 @@ defmodule Qrstorage.QrCodesTest do } @valid_audio_attrs %{ - delete_after: ~D[2010-04-17], text: "some text", hide_text: false, content_type: "audio", @@ -29,7 +28,6 @@ defmodule Qrstorage.QrCodesTest do } @valid_link_attrs %{ - delete_after: ~D[2010-04-17], text: "https://kits.blog", hide_text: false, content_type: "link", @@ -39,36 +37,11 @@ defmodule Qrstorage.QrCodesTest do } @attrs_without_hide_text %{ - delete_after: ~D[2010-04-17], text: "some text", content_type: "text", dots_type: "dots" } - @invalid_attrs %{delete_after: nil, text: nil} - - def qr_code_fixture(attrs \\ %{}) do - {:ok, qr_code} = - attrs - |> Enum.into(@valid_attrs) - |> QrCodes.create_qr_code() - - qr_code - end - - def infinity_qr_code() do - attrs = %{@valid_attrs | delete_after: Timex.end_of_year(QrCode.max_delete_after_year())} - qr_code_fixture(attrs) - end - - def overdue_qr_code() do - attrs = %{@valid_attrs | delete_after: Timex.shift(Timex.now(), months: -5)} - qr_code_fixture(attrs) - end - - def active_qr_code() do - attrs = %{@valid_attrs | delete_after: Timex.shift(Timex.now(), months: 5)} - qr_code_fixture(attrs) - end + @invalid_attrs %{delete_after_months: nil, text: nil} test "get_qr_code!/1 returns the qr_code with given id" do qr_code = qr_code_fixture() @@ -79,7 +52,7 @@ defmodule Qrstorage.QrCodesTest do test "create_qr_code/1 with valid data creates a qr_code" do assert {:ok, %QrCode{} = qr_code} = QrCodes.create_qr_code(@valid_attrs) - assert qr_code.delete_after == ~D[2010-04-17] + assert qr_code.delete_after_months == 1 assert qr_code.text == "some text" assert qr_code.hide_text == false end @@ -201,16 +174,14 @@ defmodule Qrstorage.QrCodesTest do end test "delete_old_qr_codes/0 deletes qr codes older than their delete_after date" do - infinity_qr_code = infinity_qr_code() overdue_qr_code = overdue_qr_code() - active_qr_code = active_qr_code() + active_qr_code = qr_code_fixture() {deleted_count, [deleted_code]} = QrCodes.delete_old_qr_codes() # only the overdue qr code is deleted: assert deleted_count == 1 assert deleted_code.id == overdue_qr_code.id - assert Repo.get(QrCode, infinity_qr_code.id) != nil assert Repo.get(QrCode, overdue_qr_code.id) == nil assert Repo.get(QrCode, active_qr_code.id) != nil end diff --git a/test/qrstorage/services/qr_code_service_test.exs b/test/qrstorage/services/qr_code_service_test.exs index 1f16fcc..bdf5971 100644 --- a/test/qrstorage/services/qr_code_service_test.exs +++ b/test/qrstorage/services/qr_code_service_test.exs @@ -8,7 +8,6 @@ defmodule Qrstorage.Services.QrCodeServiceTest do import ExUnit.CaptureLog @create_attrs %{ - "delete_after" => "10", "text" => "some text", "deltas" => "{\"ops\":[{\"insert\":\"Assad\\n\"}]}", "language" => nil, @@ -19,7 +18,6 @@ defmodule Qrstorage.Services.QrCodeServiceTest do } @tts_attrs %{ - "delete_after" => "10", "text" => "some text", "content_type" => "audio", "voice" => "female", @@ -29,12 +27,19 @@ defmodule Qrstorage.Services.QrCodeServiceTest do } @recording_attrs %{ - "delete_after" => "10", "text" => "a", "content_type" => "recording", "dots_type" => "dots", "hp" => nil, - "audio_file" => nil + "audio_file" => nil, + "delete_after_months" => "1" + } + + @link_attrs %{ + "text" => "https://kits.blog", + "content_type" => "link", + "dots_type" => "dots", + "delete_after_months" => "0" } describe "create_qr_code/1" do @@ -49,6 +54,46 @@ defmodule Qrstorage.Services.QrCodeServiceTest do {:error, changeset} = QrCodeService.create_qr_code(invalid_attrs) assert "can't be blank" in errors_on(changeset).text end + + test "create_qrcode/1 returns :error, when a link code has a delete_after_months date set to something else than 0" do + invalid_attrs = %{@link_attrs | "delete_after_months" => "2"} + + {:error, changeset} = QrCodeService.create_qr_code(invalid_attrs) + assert "Storage duration is invalid" in errors_on(changeset).delete_after_months + end + + test "create_qrcode/1 returns :ok, when a link code has a delete_after_months date set to 0" do + valid_attrs = %{@link_attrs | "delete_after_months" => "0"} + + {:ok, qr_code} = QrCodeService.create_qr_code(valid_attrs) + assert qr_code.text == "https://kits.blog" + end + + test "create_qrcode/1 returns :error, when a recording code has a delete_after_months date set to something else than 1" do + invalid_attrs = %{@recording_attrs | "delete_after_months" => "2"} + + {:error, changeset} = QrCodeService.create_qr_code(invalid_attrs) + assert "Storage duration is invalid" in errors_on(changeset).delete_after_months + end + + @tag :tmp_dir + test "create_qrcode/1 returns :ok, when a recording code has a delete_after_months date set to 0", %{ + tmp_dir: tmp_dir + } do + # create dummy file for test: + file_path = writeTmpFile(tmp_dir) + + params = %{ + @recording_attrs + | "audio_file" => %Plug.Upload{path: file_path, content_type: "audio/mp3", filename: "recording.mp3"}, + "delete_after_months" => "1" + } + + mockStorageServicePutObjectSuccess() + + {:ok, qr_code} = QrCodeService.create_qr_code(params) + assert qr_code.text == "a" + end end describe "create_qr_code/1 for tts codes" do diff --git a/test/qrstorage/services/translation_service_test.exs b/test/qrstorage/services/translation_service_test.exs index 2cc1d39..cbfd008 100644 --- a/test/qrstorage/services/translation_service_test.exs +++ b/test/qrstorage/services/translation_service_test.exs @@ -8,26 +8,8 @@ defmodule Qrstorage.Services.TranslationServiceTest do setup :verify_on_exit! import ExUnit.CaptureLog - @valid_attrs %{ - delete_after: ~D[2010-04-17], - text: "text", - content_type: "audio", - language: "de", - dots_type: "dots", - voice: "female" - } - - def qr_code_fixture(attrs \\ %{}) do - {:ok, qr_code} = - attrs - |> Enum.into(@valid_attrs) - |> QrCodes.create_qr_code() - - qr_code - end - defp create_audio_qr_code(_) do - %{qr_code: qr_code_fixture()} + %{qr_code: audio_qr_code_fixture()} end describe "add_translation/1 without audio code" do diff --git a/test/qrstorage/services/tts_service_test.exs b/test/qrstorage/services/tts_service_test.exs index 178e9ac..73f8086 100644 --- a/test/qrstorage/services/tts_service_test.exs +++ b/test/qrstorage/services/tts_service_test.exs @@ -10,26 +10,8 @@ defmodule Qrstorage.Services.TtsServiceTest do setup :verify_on_exit! import ExUnit.CaptureLog - @valid_attrs %{ - delete_after: ~D[2010-04-17], - text: "text", - content_type: "audio", - language: "de", - dots_type: "dots", - voice: "female" - } - - def qr_code_fixture(attrs \\ %{}) do - {:ok, qr_code} = - attrs - |> Enum.into(@valid_attrs) - |> QrCodes.create_qr_code() - - qr_code - end - defp create_audio_qr_code(_) do - %{qr_code: qr_code_fixture()} + %{qr_code: audio_qr_code_fixture()} end describe "text_to_audio/1 without audio code" do diff --git a/test/qrstorage/worker/remove_codes_worker_test.exs b/test/qrstorage/worker/remove_codes_worker_test.exs index 47d03ed..d480dec 100644 --- a/test/qrstorage/worker/remove_codes_worker_test.exs +++ b/test/qrstorage/worker/remove_codes_worker_test.exs @@ -8,18 +8,8 @@ defmodule QrstorageRemoveCodesWorkerTest do alias Qrstorage.Repo describe "perform" do - @valid_attrs %{ - delete_after: ~D[2010-04-17], - text: "some text", - hide_text: false, - content_type: "text", - language: nil, - deltas: %{"id" => "test"}, - dots_type: "dots" - } - @valid_tts_attrs %{ - delete_after: ~D[2010-04-17], + delete_after_months: 1, text: "text", content_type: "audio", language: "de", @@ -28,45 +18,19 @@ defmodule QrstorageRemoveCodesWorkerTest do } @valid_recording_attrs %{ - delete_after: ~D[2010-04-17], + delete_after_months: 1, text: "a", content_type: "recording", dots_type: "dots" } - def qr_code_fixture(attrs \\ %{}) do - {:ok, qr_code} = - attrs - |> Enum.into(@valid_attrs) - |> QrCodes.create_qr_code() - - qr_code - end - - def infinity_qr_code() do - attrs = %{@valid_attrs | delete_after: Timex.end_of_year(QrCode.max_delete_after_year())} - qr_code_fixture(attrs) - end - - def overdue_qr_code(input_attrs \\ @valid_attrs) do - attrs = %{input_attrs | delete_after: Timex.shift(Timex.now(), months: -5)} - qr_code_fixture(attrs) - end - - def active_qr_code() do - attrs = %{@valid_attrs | delete_after: Timex.shift(Timex.now(), months: 5)} - qr_code_fixture(attrs) - end - - test "perform/1 deletes qr codes with a delete_after date older than now" do - infinity_qr_code = infinity_qr_code() + test "perform/1 deletes qr codes with a delete_after_months date older than now" do overdue_qr_code = overdue_qr_code() - active_qr_code = active_qr_code() + active_qr_code = qr_code_fixture() assert :ok = perform_job(Qrstorage.Worker.RemoveCodesWorker, %{}) # only the overdue qr code is deleted: - assert Repo.get(QrCode, infinity_qr_code.id) != nil assert Repo.get(QrCode, overdue_qr_code.id) == nil assert Repo.get(QrCode, active_qr_code.id) != nil end diff --git a/test/qrstorage_web/controllers/qr_code_controller_test.exs b/test/qrstorage_web/controllers/qr_code_controller_test.exs index fc57803..6c59ff2 100644 --- a/test/qrstorage_web/controllers/qr_code_controller_test.exs +++ b/test/qrstorage_web/controllers/qr_code_controller_test.exs @@ -10,8 +10,8 @@ defmodule QrstorageWeb.QrCodeControllerTest do import ExUnit.CaptureLog @create_attrs %{ - delete_after: "10", text: "some text", + delete_after_months: 1, deltas: "{\"ops\":[{\"insert\":\"Assad\\n\"}]}", language: nil, content_type: "text", @@ -20,9 +20,8 @@ defmodule QrstorageWeb.QrCodeControllerTest do hp: nil } - @invalid_attrs %{delete_after: "10", text: nil, language: nil, content_type: "text"} + @invalid_attrs %{text: nil, language: nil, content_type: "text"} @fixture_attrs %{ - delete_after: ~D[2011-05-18], text: "some text", language: nil, content_type: "text", @@ -59,37 +58,14 @@ defmodule QrstorageWeb.QrCodeControllerTest do assert html_response(conn, 200) =~ "Oops, something went wrong" end - test "uses 1 hour as the delete after date for links", %{conn: conn} do - link_attrs = %{@create_attrs | content_type: "link", text: "https://kits.blog"} + test "uses 0 months as the delete after months date for links", %{conn: conn} do + link_attrs = %{@create_attrs | delete_after_months: 0, content_type: "link", text: "https://kits.blog"} conn = post(conn, Routes.qr_code_path(conn, :create), qr_code: link_attrs) assert %{id: id} = redirected_params(conn) qr_code = QrCode |> Repo.get!(id) - assert qr_code.delete_after <= Timex.shift(Timex.now(), hours: 1) - end - - test "uses QrCode max year as deletion date for infinity", %{conn: conn} do - link_attrs = %{@create_attrs | delete_after: "0"} - conn = post(conn, Routes.qr_code_path(conn, :create), qr_code: link_attrs) - assert %{id: id} = redirected_params(conn) - - qr_code = QrCode |> Repo.get!(id) - assert qr_code.delete_after.year == QrCode.max_delete_after_year() - end - - test "adds the admin_url_id to the flash message for QR codes that are stored indefinitely", - %{conn: conn} do - link_attrs = %{@create_attrs | delete_after: "0"} - conn = post(conn, Routes.qr_code_path(conn, :create), qr_code: link_attrs) - assert Phoenix.Flash.get(conn.assigns.flash, :admin_url_id) != nil - end - - test "does not add the admin_url_id to the flash message for QR codes that are not stored indefinitely", - %{conn: conn} do - link_attrs = %{@create_attrs | delete_after: "1"} - conn = post(conn, Routes.qr_code_path(conn, :create), qr_code: link_attrs) - assert Phoenix.Flash.get(conn.assigns.flash, :admin_url_id) == nil + assert qr_code.delete_after_months == 0 end end diff --git a/test/qrstorage_web/views/qr_code_view_test.exs b/test/qrstorage_web/views/qr_code_view_test.exs index de42d25..1284cee 100644 --- a/test/qrstorage_web/views/qr_code_view_test.exs +++ b/test/qrstorage_web/views/qr_code_view_test.exs @@ -17,16 +17,10 @@ defmodule QrstorageWeb.QrCodeViewTest do end describe "show_delete_after_text/1" do - test "shows indefinitely for qr codes that should not be automatically deleted" do - delete_after = Timex.end_of_year(QrCode.max_delete_after_year()) - text = show_delete_after_text(delete_after) - assert text =~ "indefinitely" - end - test "shows relative deletion date for qr codes that should be deleted automatically" do # we add one hour, since Timex seems to round down - e.g. 5 months, minus a few milliseconds results in "in 4 months" - delete_after = Timex.shift(Timex.now(), months: 5, hours: 1) - text = show_delete_after_text(delete_after) + delete_after_code = %QrCode{delete_after_months: 5, last_accessed_at: Timex.now()} + text = show_delete_after_text(delete_after_code) assert text =~ "in 5 months" end end diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 096e125..3a95a38 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -19,6 +19,7 @@ defmodule Qrstorage.DataCase do using do quote do alias Qrstorage.Repo + alias Qrstorage.QrCodes import Ecto import Ecto.Changeset @@ -28,6 +29,52 @@ defmodule Qrstorage.DataCase do def qr_code_count() do Qrstorage.Repo.one(from code in Qrstorage.QrCodes.QrCode, select: count("1")) end + + @valid_attrs %{ + delete_after_months: 1, + text: "some text", + hide_text: false, + content_type: "text", + language: nil, + hp: nil, + deltas: %{"id" => "test"}, + dots_type: "dots" + } + + @valid_audio_attrs %{ + delete_after_months: 1, + text: "text", + content_type: "audio", + language: "de", + dots_type: "dots", + voice: "female" + } + + def qr_code_fixture(attrs \\ %{}) do + {:ok, qr_code} = + attrs + |> Enum.into(@valid_attrs) + |> QrCodes.create_qr_code() + + qr_code + end + + def audio_qr_code_fixture(attrs \\ %{}) do + {:ok, qr_code} = + attrs + |> Enum.into(@valid_audio_attrs) + |> QrCodes.create_qr_code() + + qr_code + end + + def overdue_qr_code(attrs \\ %{}) do + attrs = Map.merge(@valid_attrs, attrs) + qr_code = qr_code_fixture(attrs) + last_access_date = Timex.shift(Timex.now(), months: -7) + Repo.update!(Ecto.Changeset.cast(qr_code, %{last_accessed_at: last_access_date}, [:last_accessed_at])) + qr_code + end end end