feat: step 5 claimed done

This commit is contained in:
2026-04-27 22:36:53 +02:00
parent 0e1d8852f7
commit 2f09bf527d
20 changed files with 1740 additions and 115 deletions

View File

@@ -51,13 +51,7 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert result.project_id == project.id
end
test "validate_translations returns an editor payload with current translation gaps", %{project: project} do
assert {:ok, _metadata} =
BDS.Metadata.update_project_metadata(project.id, %{
main_language: "en",
blog_languages: ["en", "de"]
})
test "validate_translations returns an editor payload with current translation gaps", %{project: project, temp_dir: temp_dir} do
assert {:ok, post} =
BDS.Posts.create_post(%{
project_id: project.id,
@@ -66,7 +60,37 @@ defmodule BDS.Desktop.ShellCommandsTest do
language: "en"
})
assert {:ok, _published_post} = BDS.Posts.publish_post(post.id)
assert {:ok, published_post} = BDS.Posts.publish_post(post.id)
assert {:ok, translation} =
BDS.Posts.upsert_post_translation(post.id, "de", %{
title: "Hallo",
content: "Welt",
status: :published
})
translation_id = translation.id
invalid_file_path =
Path.join([
temp_dir,
Path.dirname(published_post.file_path),
"#{published_post.slug}.en.md"
])
File.write!(
invalid_file_path,
[
"---",
"translationFor: #{post.id}",
"language: en",
"title: Wrong Language",
"---",
"Invalid translation",
""
]
|> Enum.join("\n")
)
assert {:ok, result} = ShellCommands.execute("validate_translations")
@@ -79,9 +103,28 @@ defmodule BDS.Desktop.ShellCommandsTest do
assert completed.group_name == "Validation"
assert completed.result.kind == "open_editor"
assert completed.result.route == "translation_validation"
assert completed.result.payload.summary.missing_count == 1
assert completed.result.payload.checked_database_row_count == 1
assert completed.result.payload.checked_filesystem_file_count == 1
post_id = post.id
assert [%{"language" => "de", "post_id" => ^post_id}] = completed.result.payload.missing
assert [
%{
"issue" => "content-in-database",
"translation_for" => ^post_id,
"translation_id" => ^translation_id,
"translation_language" => "de"
}
] = completed.result.payload.invalid_database_rows
assert [
%{
"issue" => "same-language-as-canonical",
"translation_for" => ^post_id,
"translation_language" => "en",
"file_path" => ^invalid_file_path
}
] = completed.result.payload.invalid_filesystem_files
end
test "validate_site queues a tracked validation task and returns the report as an editor payload" do

View File

@@ -1736,6 +1736,160 @@ defmodule BDS.Desktop.ShellLiveTest do
refute chat_html =~ "Desktop workbench content routed through the Elixir shell."
end
test "chat editor renders legacy model controls, tool markers, and structured tool surfaces" do
assert {:ok, conversation} = AI.start_chat(%{title: "Editor Chat", model: "gpt-4.1"})
now = Persistence.now_ms()
Repo.insert!(
BDS.AI.ChatMessage.changeset(%BDS.AI.ChatMessage{}, %{
conversation_id: conversation.id,
role: :user,
content: "Show me a table",
created_at: now
})
)
Repo.insert!(
BDS.AI.ChatMessage.changeset(%BDS.AI.ChatMessage{}, %{
conversation_id: conversation.id,
role: :assistant,
content: "Here is the current summary.",
tool_calls:
Jason.encode!([
%{
"id" => "call-table",
"name" => "render_table",
"arguments" => %{"title" => "Blog Stats", "columns" => ["Metric", "Value"]}
}
]),
created_at: now + 1
})
)
Repo.insert!(
BDS.AI.ChatMessage.changeset(%BDS.AI.ChatMessage{}, %{
conversation_id: conversation.id,
role: :tool,
tool_call_id: "call-table",
content:
Jason.encode!(%{
"type" => "table",
"title" => "Blog Stats",
"columns" => ["Metric", "Value"],
"rows" => [["Posts", "1"], ["Media", "0"]]
}),
created_at: now + 2
})
)
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
html =
render_click(view, "pin_sidebar_item", %{
"route" => "chat",
"id" => conversation.id,
"title" => conversation.title,
"subtitle" => conversation.model || "chat"
})
assert html =~ ~s(data-testid="chat-model-selector-button")
assert html =~ "gpt-4.1"
assert html =~ ~s(data-testid="chat-tool-marker")
assert html =~ "render_table"
assert html =~ ~s(data-testid="chat-tool-surface")
assert html =~ "Blog Stats"
assert html =~ "Metric"
assert html =~ "Posts"
end
test "translation validation route renders dedicated cards and fix controls", %{project: project, temp_dir: temp_dir} do
assert {:ok, _metadata} =
BDS.Metadata.update_project_metadata(project.id, %{
main_language: "en",
blog_languages: ["en", "de"]
})
assert {:ok, post} =
Posts.create_post(%{
project_id: project.id,
title: "Hello",
content: "World",
language: "en"
})
assert {:ok, published_post} = Posts.publish_post(post.id)
assert {:ok, _translation} =
Posts.upsert_post_translation(post.id, "de", %{
title: "Hallo",
content: "Welt",
status: :published
})
invalid_file_path =
Path.join([
temp_dir,
Path.dirname(published_post.file_path),
"#{published_post.slug}.en.md"
])
File.write!(
invalid_file_path,
[
"---",
"translationFor: #{post.id}",
"language: en",
"title: Wrong Language",
"---",
"Invalid translation",
""
]
|> Enum.join("\n")
)
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
assert {:ok, queued} = BDS.Desktop.ShellCommands.execute("validate_translations")
completed_task!(queued.task_id)
send(view.pid, :refresh_task_status)
html = render(view)
assert html =~ ~s(class="translation-validation-view")
assert html =~ ~s(data-testid="translation-validation-revalidate")
assert html =~ ~s(data-testid="translation-validation-fix")
assert html =~ ~s(data-testid="translation-validation-card")
assert html =~ invalid_file_path
end
test "git diff route renders a structured Monaco diff surface for working tree changes", %{temp_dir: temp_dir} do
posts_dir = Path.join(temp_dir, "posts")
File.mkdir_p!(posts_dir)
file_path = Path.join(posts_dir, "first.md")
File.write!(file_path, "Old content\n")
init_git_repo!(temp_dir, "initial")
File.write!(file_path, "New content\n")
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)
html =
render_click(view, "pin_sidebar_item", %{
"route" => "git_diff",
"id" => "git-working-tree",
"title" => "Working tree",
"subtitle" => "Working tree and history"
})
assert html =~ ~s(class="git-diff-view")
assert html =~ ~s(data-testid="git-diff-file-select")
assert html =~ "posts/first.md"
assert html =~ ~s(phx-hook="MonacoDiffEditor")
refute html =~ ~s(<pre><code>)
end
test "settings sidebar categories render the full old-app section model and target the requested section" do
{:ok, view, _html} = live_isolated(build_conn(), BDS.Desktop.ShellLive)

View File

@@ -72,6 +72,26 @@ defmodule BDS.GitTest do
assert repo.current_branch == "main"
end
test "get_diff_content returns HEAD and working tree content for a changed file", %{
project: project,
project_dir: project_dir
} do
posts_dir = Path.join(project_dir, "posts")
File.mkdir_p!(posts_dir)
relative_path = "posts/first.md"
full_path = Path.join(project_dir, relative_path)
File.write!(full_path, "Old content\n")
init_git_repo!(project_dir, "initial")
File.write!(full_path, "New content\n")
assert {:ok, diff} = Git.get_diff_content(project.id, relative_path)
assert diff.file_path == relative_path
assert diff.original == "Old content\n"
assert diff.modified == "New content\n"
end
test "remote_state reports upstream ahead and behind counts", %{project: project} do
runner = fake_runner(fn
"git", ["rev-parse", "--abbrev-ref", "HEAD"], _opts -> {"main\n", 0}
@@ -146,4 +166,18 @@ defmodule BDS.GitTest do
defp fake_runner(handler) do
fn command, args, opts -> handler.(command, args, opts) end
end
defp init_git_repo!(project_dir, message) do
run_git!(project_dir, ["init", "-b", "master"])
run_git!(project_dir, ["config", "user.name", "bDS Tests"])
run_git!(project_dir, ["config", "user.email", "tests@example.com"])
run_git!(project_dir, ["add", "-A"])
run_git!(project_dir, ["commit", "-m", message])
end
defp run_git!(dir, args) do
{output, status} = System.cmd("git", args, cd: dir, stderr_to_stdout: true)
assert status == 0, output
end
end

View File

@@ -705,6 +705,75 @@ defmodule BDS.PostsTest do
assert {:ok, %{indexed: 3, total: 3}} = BDS.Embeddings.get_indexing_progress(project.id)
end
test "validate_translations and fix_invalid_translations follow the legacy invalid-translation workflow",
%{project: project} do
assert {:ok, post} =
BDS.Posts.create_post(%{
project_id: project.id,
title: "Source Post",
content: "Canonical body",
language: "en"
})
assert {:ok, published_post} = BDS.Posts.publish_post(post.id)
assert {:ok, translation} =
BDS.Posts.upsert_post_translation(post.id, "de", %{
title: "Translated Post",
content: "Translated body",
status: :published
})
invalid_file_path =
Path.join([
BDS.Projects.project_data_dir(project),
Path.dirname(published_post.file_path),
"#{published_post.slug}.en.md"
])
File.write!(
invalid_file_path,
[
"---",
"translationFor: #{post.id}",
"language: en",
"title: Invalid Same Language",
"---",
"Wrong translation",
""
]
|> Enum.join("\n")
)
assert {:ok, report} = BDS.Posts.validate_translations(project.id)
assert report.checked_database_row_count == 1
assert report.checked_filesystem_file_count == 1
assert [db_issue] = report.invalid_database_rows
assert db_issue.issue == "content-in-database"
assert db_issue.translation_id == translation.id
assert db_issue.translation_for == post.id
assert db_issue.translation_language == "de"
assert [file_issue] = report.invalid_filesystem_files
assert file_issue.issue == "same-language-as-canonical"
assert file_issue.translation_for == post.id
assert file_issue.translation_language == "en"
assert file_issue.file_path == invalid_file_path
assert {:ok, result} = BDS.Posts.fix_invalid_translations(report)
assert result.deleted_database_rows == 0
assert result.deleted_files == 1
assert result.flushed_translations == 1
saved_translation = BDS.Repo.get!(BDS.Posts.Translation, translation.id)
assert saved_translation.content == nil
assert is_binary(saved_translation.file_path)
assert File.exists?(Path.join(BDS.Projects.project_data_dir(project), saved_translation.file_path))
refute File.exists?(invalid_file_path)
end
def handle_repo_query(_event, _measurements, metadata, owner_pid) do
send(owner_pid, {:repo_query, metadata.query || ""})
end