feat: closing last gaps in backend functions we have available
This commit is contained in:
62
test/bds/cli_sync_test.exs
Normal file
62
test/bds/cli_sync_test.exs
Normal file
@@ -0,0 +1,62 @@
|
||||
defmodule BDS.CliSyncTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias BDS.CliSync
|
||||
alias BDS.Repo
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
Repo.delete_all(BDS.CliSync.Notification)
|
||||
:ok
|
||||
end
|
||||
|
||||
test "cli mutations are written to db_notifications, processed on file change, and marked seen" do
|
||||
assert {:ok, notification} = CliSync.cli_mutation_performed("post", "post-1", :updated)
|
||||
assert notification.from_cli == true
|
||||
assert notification.seen_at == nil
|
||||
|
||||
assert {:ok, processed} = CliSync.db_file_change_detected()
|
||||
assert [%{entity_type: "post", entity_id: "post-1", action: :updated}] = processed
|
||||
|
||||
seen_notification = Repo.get!(BDS.CliSync.Notification, notification.id)
|
||||
assert is_integer(seen_notification.seen_at)
|
||||
end
|
||||
|
||||
test "processed notifications are pruned after one hour and unprocessed notifications after one day" do
|
||||
now = BDS.Persistence.now_ms()
|
||||
|
||||
Repo.insert!(%BDS.CliSync.Notification{
|
||||
entity_type: "post",
|
||||
entity_id: "processed-old",
|
||||
action: :updated,
|
||||
from_cli: true,
|
||||
seen_at: now - 10,
|
||||
created_at: now - 3_600_001
|
||||
})
|
||||
|
||||
Repo.insert!(%BDS.CliSync.Notification{
|
||||
entity_type: "media",
|
||||
entity_id: "unprocessed-old",
|
||||
action: :deleted,
|
||||
from_cli: true,
|
||||
seen_at: nil,
|
||||
created_at: now - 86_400_001
|
||||
})
|
||||
|
||||
Repo.insert!(%BDS.CliSync.Notification{
|
||||
entity_type: "script",
|
||||
entity_id: "fresh",
|
||||
action: :created,
|
||||
from_cli: true,
|
||||
seen_at: nil,
|
||||
created_at: now
|
||||
})
|
||||
|
||||
assert {:ok, %{processed: 1, unprocessed: 1}} = CliSync.prune_notifications(now)
|
||||
|
||||
remaining_ids = Repo.all(from notification in BDS.CliSync.Notification, select: notification.entity_id)
|
||||
assert remaining_ids == ["fresh"]
|
||||
end
|
||||
end
|
||||
@@ -103,10 +103,16 @@ defmodule BDS.GenerationTest do
|
||||
"feed.xml",
|
||||
"atom.xml",
|
||||
"calendar.json",
|
||||
"pagefind/index.json",
|
||||
"pagefind/pagefind-ui.css",
|
||||
"pagefind/pagefind-ui.js",
|
||||
"de/404.html",
|
||||
"de/index.html",
|
||||
"de/feed.xml",
|
||||
"de/atom.xml"
|
||||
"de/atom.xml",
|
||||
"de/pagefind/index.json",
|
||||
"de/pagefind/pagefind-ui.css",
|
||||
"de/pagefind/pagefind-ui.js"
|
||||
]
|
||||
|
||||
assert Enum.sort(Enum.map(result.generated_files, & &1.relative_path)) ==
|
||||
@@ -127,7 +133,7 @@ defmodule BDS.GenerationTest do
|
||||
Metadata.update_project_metadata(project.id, %{
|
||||
public_url: "https://example.com/blog",
|
||||
main_language: "en",
|
||||
blog_languages: ["en"]
|
||||
blog_languages: ["en", "de"]
|
||||
})
|
||||
|
||||
assert {:ok, list_template} =
|
||||
@@ -178,6 +184,25 @@ defmodule BDS.GenerationTest do
|
||||
post_html = File.read!(Path.join([temp_dir, "html", post_path]))
|
||||
assert post_html =~ "post-template"
|
||||
assert post_html =~ "Rendered body"
|
||||
|
||||
assert "pagefind/index.json" in relative_paths
|
||||
assert "pagefind/pagefind-ui.js" in relative_paths
|
||||
assert "de/pagefind/index.json" in relative_paths
|
||||
|
||||
pagefind_index =
|
||||
Path.join([temp_dir, "html", "pagefind", "index.json"])
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|
||||
assert pagefind_index["language"] == "en"
|
||||
assert Enum.any?(pagefind_index["pages"], &(&1["url"] == "/#{post_path}"))
|
||||
|
||||
de_pagefind_index =
|
||||
Path.join([temp_dir, "html", "de", "pagefind", "index.json"])
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|
||||
assert de_pagefind_index["language"] == "de"
|
||||
end
|
||||
|
||||
test "generation renders copied starter templates with partials, i18n, and markdown", %{
|
||||
|
||||
@@ -2,6 +2,7 @@ defmodule BDS.MCPTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias BDS.Media.Media
|
||||
alias BDS.MCP.ProposalStore
|
||||
alias BDS.Repo
|
||||
alias BDS.Scripts.Script
|
||||
alias BDS.Templates.Template
|
||||
@@ -168,6 +169,39 @@ defmodule BDS.MCPTest do
|
||||
assert_raise Ecto.NoResultsError, fn -> BDS.Posts.get_post!(draft_post_id) end
|
||||
end
|
||||
|
||||
test "proposal lifecycle is persisted with pending, accepted, discarded, and expired statuses" do
|
||||
assert {:ok, accepted_result} =
|
||||
BDS.MCP.call_tool("draft_post", %{title: "Accept Me", content: "Body"})
|
||||
|
||||
accepted_id = accepted_result["proposal_id"]
|
||||
assert ProposalStore.get(accepted_id).status == :pending
|
||||
|
||||
assert {:ok, _accepted} = BDS.MCP.call_tool("accept_proposal", %{proposalId: accepted_id})
|
||||
|
||||
accepted_proposal = ProposalStore.get(accepted_id)
|
||||
assert accepted_proposal.status == :accepted
|
||||
assert accepted_proposal.entity_id == accepted_result["post"]["id"]
|
||||
|
||||
assert {:ok, discarded_result} =
|
||||
BDS.MCP.call_tool("draft_post", %{title: "Discard Me Later", content: "Body"})
|
||||
|
||||
discarded_id = discarded_result["proposal_id"]
|
||||
assert {:ok, _discarded} = BDS.MCP.call_tool("discard_proposal", %{proposalId: discarded_id})
|
||||
|
||||
discarded_proposal = ProposalStore.get(discarded_id)
|
||||
assert discarded_proposal.status == :discarded
|
||||
|
||||
expired =
|
||||
ProposalStore.create("draft_post", %{"post_id" => "expired-post"},
|
||||
entity_id: "expired-post",
|
||||
ttl_ms: -1
|
||||
)
|
||||
|
||||
expired_proposals = ProposalStore.cleanup_expired()
|
||||
assert Enum.any?(expired_proposals, &(&1.id == expired.id and &1.status == :expired))
|
||||
assert ProposalStore.get(expired.id).status == :expired
|
||||
end
|
||||
|
||||
test "resource listing and reads follow old app naming for implemented resources", %{project: project} do
|
||||
assert {:ok, post} =
|
||||
BDS.Posts.create_post(%{
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
defmodule BDS.PublishingTest do
|
||||
use ExUnit.Case, async: false
|
||||
|
||||
alias BDS.Repo
|
||||
|
||||
setup do
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.checkout(BDS.Repo)
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.allow(BDS.Repo, self(), Process.whereis(BDS.Publishing))
|
||||
|
||||
temp_dir =
|
||||
Path.join(System.tmp_dir!(), "bds-publishing-#{System.unique_integer([:positive])}")
|
||||
@@ -277,6 +280,43 @@ defmodule BDS.PublishingTest do
|
||||
assert elem(html_upload, 1) == ["-q", html_index, "deploy@example.com:/srv/blog/index.html"]
|
||||
end
|
||||
|
||||
test "publish jobs survive a publishing server restart because they are persisted", %{
|
||||
project: project,
|
||||
temp_dir: temp_dir
|
||||
} do
|
||||
File.mkdir_p!(Path.join([temp_dir, "html"]))
|
||||
File.write!(Path.join([temp_dir, "html", "index.html"]), "<html />")
|
||||
|
||||
credentials = %{
|
||||
ssh_host: "example.com",
|
||||
ssh_user: "deploy",
|
||||
ssh_remote_path: "/srv/blog",
|
||||
ssh_mode: :rsync
|
||||
}
|
||||
|
||||
assert {:ok, job} =
|
||||
BDS.Publishing.upload_site(project.id, credentials, uploader: fn _, _, _ -> :ok end)
|
||||
|
||||
assert wait_for_publish_job(job.id, &(&1.status == :completed)).status == :completed
|
||||
|
||||
persisted_before_restart = Repo.get!(BDS.Publishing.PublishJob, job.id)
|
||||
assert persisted_before_restart.status == :completed
|
||||
|
||||
publishing_pid = Process.whereis(BDS.Publishing)
|
||||
ref = Process.monitor(publishing_pid)
|
||||
:ok = GenServer.stop(BDS.Publishing, :normal)
|
||||
assert_receive {:DOWN, ^ref, :process, ^publishing_pid, _reason}
|
||||
|
||||
restarted_pid = wait_for_process(BDS.Publishing)
|
||||
refute restarted_pid == publishing_pid
|
||||
|
||||
:ok = Ecto.Adapters.SQL.Sandbox.allow(BDS.Repo, self(), restarted_pid)
|
||||
|
||||
persisted_after_restart = BDS.Publishing.get_job(job.id)
|
||||
assert persisted_after_restart.id == job.id
|
||||
assert persisted_after_restart.status == :completed
|
||||
end
|
||||
|
||||
defp collect_command_runs(acc \\ []) do
|
||||
receive do
|
||||
{:command_run, command, args, _opts} -> collect_command_runs([{command, args} | acc])
|
||||
@@ -301,4 +341,21 @@ defmodule BDS.PublishingTest do
|
||||
defp wait_for_publish_job(_job_id, _predicate, 0) do
|
||||
flunk("publish job did not reach expected state")
|
||||
end
|
||||
|
||||
defp wait_for_process(name, attempts \\ 100)
|
||||
|
||||
defp wait_for_process(name, attempts) when attempts > 0 do
|
||||
case Process.whereis(name) do
|
||||
nil ->
|
||||
Process.sleep(20)
|
||||
wait_for_process(name, attempts - 1)
|
||||
|
||||
pid ->
|
||||
pid
|
||||
end
|
||||
end
|
||||
|
||||
defp wait_for_process(name, 0) do
|
||||
flunk("process #{inspect(name)} did not restart")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -197,6 +197,29 @@ defmodule BDS.Repo.SchemaMigrationTest do
|
||||
"created_at",
|
||||
"updated_at"
|
||||
],
|
||||
"publish_jobs" => [
|
||||
"id",
|
||||
"project_id",
|
||||
"ssh_host",
|
||||
"ssh_user",
|
||||
"ssh_remote_path",
|
||||
"ssh_mode",
|
||||
"status",
|
||||
"task_id",
|
||||
"targets",
|
||||
"error",
|
||||
"inserted_at",
|
||||
"updated_at"
|
||||
],
|
||||
"mcp_proposals" => [
|
||||
"id",
|
||||
"kind",
|
||||
"status",
|
||||
"entity_id",
|
||||
"data",
|
||||
"created_at",
|
||||
"expires_at"
|
||||
],
|
||||
"db_notifications" => [
|
||||
"id",
|
||||
"entity_type",
|
||||
@@ -251,6 +274,12 @@ defmodule BDS.Repo.SchemaMigrationTest do
|
||||
"generated_file_hashes_project_path_idx"
|
||||
) == ["project_id", "relative_path"]
|
||||
|
||||
assert unique_index_columns("mcp_proposals", "mcp_proposals_entity_idx") == [
|
||||
"kind",
|
||||
"entity_id",
|
||||
"status"
|
||||
]
|
||||
|
||||
assert unique_index_columns("dismissed_duplicate_pairs", "dismissed_pairs_idx") == [
|
||||
"project_id",
|
||||
"post_id_a",
|
||||
@@ -296,6 +325,7 @@ defmodule BDS.Repo.SchemaMigrationTest do
|
||||
assert column_metadata("posts", "file_path")[:default] == ""
|
||||
assert column_metadata("posts", "do_not_translate")[:default] == "false"
|
||||
assert column_metadata("post_media", "sort_order")[:default] == "0"
|
||||
assert column_metadata("publish_jobs", "status")[:default] == "pending"
|
||||
assert column_metadata("db_notifications", "from_cli")[:default] == "true"
|
||||
|
||||
assert foreign_keys("posts") == [%{from: "project_id", table: "projects", to: "id"}]
|
||||
@@ -316,6 +346,10 @@ defmodule BDS.Repo.SchemaMigrationTest do
|
||||
%{from: "translation_for", table: "media", to: "id"}
|
||||
]
|
||||
|
||||
assert foreign_keys("publish_jobs") == [
|
||||
%{from: "project_id", table: "projects", to: "id"}
|
||||
]
|
||||
|
||||
assert foreign_keys("chat_messages") == [
|
||||
%{from: "conversation_id", table: "chat_conversations", to: "id"}
|
||||
]
|
||||
|
||||
@@ -275,11 +275,17 @@ defmodule BDS.SearchTest do
|
||||
languages = BDS.Search.list_stemmer_languages()
|
||||
|
||||
assert is_list(languages)
|
||||
assert length(languages) == 24
|
||||
assert "en" in languages
|
||||
assert "de" in languages
|
||||
assert "fr" in languages
|
||||
assert "it" in languages
|
||||
assert "es" in languages
|
||||
assert "ar" in languages
|
||||
assert "ca" in languages
|
||||
assert "el" in languages
|
||||
assert "ga" in languages
|
||||
assert "hi" in languages
|
||||
assert Enum.uniq(languages) == languages
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user