Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-14 - Add focus visible state to deployment tabs
**Learning:** Deployment tab buttons were missing standard `focus-visible` styles which meant keyboard navigation wasn't clear. Added focus styles consistent with other components.
**Action:** Always verify keyboard navigation by tabbing through all interactive elements in new components.
24 changes: 17 additions & 7 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,24 @@ else
System.get_env("CK_TEST_DB") ||
Path.expand("../priv/repo/controlkeel_test.db", __DIR__)

pool_opts =
if System.get_env("CK_TEST_SUBPROCESS") do
[pool_size: 10]
else
[
# SQLite-backed tests are more stable with a single pooled connection because
# LiveView and benchmark flows can otherwise compete for overlapping write locks.
pool_size: 1,
pool: Ecto.Adapters.SQL.Sandbox
]
end

config :controlkeel, ControlKeel.Repo.Local,
database: test_database,
busy_timeout: 15_000,
# SQLite-backed tests are more stable with a single pooled connection because
# LiveView and benchmark flows can otherwise compete for overlapping write locks.
pool_size: 1,
pool: Ecto.Adapters.SQL.Sandbox,
journal_mode: :wal
[
database: test_database,
busy_timeout: 15_000,
journal_mode: :wal
] ++ pool_opts
end

# We don't run a server during test. If one is required,
Expand Down
2 changes: 1 addition & 1 deletion lib/controlkeel_web/components/deployment_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule ControlKeelWeb.DeploymentComponents do
phx-click="set_tab"
phx-value-tab={@name}
class={[
"rounded-full px-4 py-1.5 text-sm font-semibold capitalize transition cursor-pointer",
"rounded-full px-4 py-1.5 text-sm font-semibold capitalize transition cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 focus-visible:ring-offset-background",
@tab == @name && "bg-primary/15 text-primary ring-1 ring-primary/30",
@tab != @name && "text-muted-foreground hover:bg-muted hover:text-foreground"
]}
Expand Down
2 changes: 1 addition & 1 deletion test/controlkeel/accounts/data_lifecycle_test.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
defmodule ControlKeel.Accounts.DataLifecycleTest do
@modagedoc """
@moduledoc """
User/org erasure (issue #266).

The contract under test: identity is purged, the audit *record* survives with
Expand Down
14 changes: 7 additions & 7 deletions test/controlkeel/accounts_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ defmodule ControlKeel.AccountsTest do
{:ok, owner: owner, admin: admin, invitee: invitee, org: org}
end

test "owner can invite at any role", %{owner: owner, invitee: invitee, org: org} do
test "owner can invite at any role", %{owner: owner, org: org} do
for role <- ["owner", "admin", "member", "viewer"] do
# Fresh invitee per role to avoid already_member collisions.
{:ok, u} = Accounts.create_user(%{email: "invitee-#{role}@example.com"})
Expand All @@ -298,7 +298,7 @@ defmodule ControlKeel.AccountsTest do
end
end

test "admin can invite member/viewer", %{admin: admin, invitee: invitee, org: org} do
test "admin can invite member/viewer", %{admin: admin, org: org} do
for role <- ["member", "viewer"] do
{:ok, u} = Accounts.create_user(%{email: "invitee-#{role}-a@example.com"})

Expand Down Expand Up @@ -559,7 +559,7 @@ defmodule ControlKeel.AccountsTest do
assert {:error, :unauthorized} = Accounts.revoke_membership(m.id, member.id)
end

test "admin can revoke others but not self", %{owner: owner, org: org} do
test "admin can revoke others but not self", %{org: org} do
{:ok, admin} = Accounts.create_user(%{email: "admin@example.com"})
{:ok, _, raw} = Accounts.invite_member(admin.id, org.id, role: "admin")
{:ok, _} = Accounts.accept_invitation(raw, admin.id)
Expand All @@ -571,7 +571,7 @@ defmodule ControlKeel.AccountsTest do
test "non-admin cannot revoke", %{membership: m} do
{:ok, outsider} = Accounts.create_user(%{email: "out@example.com"})

{:ok, org2} =
{:ok, _org2} =
Accounts.create_org_with_owner(outsider.id, %{
name: "O2",
slug: "o2-#{System.unique_integer([:positive])}"
Expand All @@ -580,7 +580,7 @@ defmodule ControlKeel.AccountsTest do
assert {:error, :unauthorized} = Accounts.revoke_membership(m.id, outsider.id)
end

test "admin cannot revoke an owner", %{owner: owner, org: org} do
test "admin cannot revoke an owner", %{org: org} do
{:ok, admin} = Accounts.create_user(%{email: "admin@example.com"})
{:ok, _, raw} = Accounts.invite_member(admin.id, org.id, role: "admin")
{:ok, _} = Accounts.accept_invitation(raw, admin.id)
Expand All @@ -594,7 +594,7 @@ defmodule ControlKeel.AccountsTest do
assert {:error, :cannot_revoke_owner} = Accounts.revoke_membership(owner2_m.id, admin.id)
end

test "admin cannot revoke another admin", %{owner: owner, org: org} do
test "admin cannot revoke another admin", %{org: org} do
{:ok, admin1} = Accounts.create_user(%{email: "admin1@example.com"})
{:ok, _, raw1} = Accounts.invite_member(admin1.id, org.id, role: "admin")
{:ok, _} = Accounts.accept_invitation(raw1, admin1.id)
Expand Down Expand Up @@ -724,7 +724,7 @@ defmodule ControlKeel.AccountsTest do
test "outsider cannot change roles", %{membership: m} do
{:ok, outsider} = Accounts.create_user(%{email: "out@example.com"})

{:ok, org2} =
{:ok, _org2} =
Accounts.create_org_with_owner(outsider.id, %{
name: "O2",
slug: "o2-#{System.unique_integer([:positive])}"
Expand Down
6 changes: 4 additions & 2 deletions test/controlkeel/bin_wrapper_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ defmodule ControlKeel.BinWrapperTest do
{"CK_CLI_MODE", "1"},
{"LOGGER_LEVEL", "error"},
{"DATABASE_PATH", tmp_db},
{"CK_TEST_DB", tmp_db}
{"CK_TEST_DB", tmp_db},
{"CK_TEST_SUBPROCESS", "1"}
]

%{env: env}
Expand Down Expand Up @@ -69,7 +70,8 @@ defmodule ControlKeel.BinWrapperTest do
{"CK_PROJECT_ROOT", File.cwd!()},
{"LOGGER_LEVEL", "error"},
{"DATABASE_PATH", tmp_db},
{"CK_TEST_DB", tmp_db}
{"CK_TEST_DB", tmp_db},
{"CK_TEST_SUBPROCESS", "1"}
]

{output, 0} = System.cmd(@wrapper, ["context", "--session-id", "1", "--json"], env: env)
Expand Down
22 changes: 21 additions & 1 deletion test/controlkeel/mcp/server_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
end

@tag :capture_log
test "MCP launcher emits only one JSONL response on real stdout" do

Check failure on line 53 in test/controlkeel/mcp/server_test.exs

View workflow job for this annotation

GitHub Actions / test

test MCP launcher emits only one JSONL response on real stdout (ControlKeel.MCP.ServerTest)
request =
Jason.encode!(%{
"jsonrpc" => "2.0",
Expand All @@ -59,6 +59,23 @@
"params" => %{"protocolVersion" => "2024-11-05"}
})

tmp_db =
Path.join(
System.tmp_dir!(),
"controlkeel-mcp-server-#{System.unique_integer([:positive])}.db"
)

src_db = ControlKeel.Repo.Local.config()[:database]
File.cp!(src_db, tmp_db)
if File.exists?(src_db <> "-wal"), do: File.cp!(src_db <> "-wal", tmp_db <> "-wal")
if File.exists?(src_db <> "-shm"), do: File.cp!(src_db <> "-shm", tmp_db <> "-shm")

on_exit(fn ->
File.rm(tmp_db)
File.rm(tmp_db <> "-wal")
File.rm(tmp_db <> "-shm")
end)

shell =
~S(printf '%s\n' "$MCP_REQUEST" | "$MCP_LAUNCHER" --project-root "$CK_PROJECT_ROOT")

Expand All @@ -69,7 +86,10 @@
{"MCP_REQUEST", request},
{"MCP_LAUNCHER", @launcher},
{"CK_PROJECT_ROOT", File.cwd!()},
{"MIX_ENV", "test"}
{"MIX_ENV", "test"},
{"DATABASE_PATH", tmp_db},
{"CK_TEST_DB", tmp_db},
{"CK_TEST_SUBPROCESS", "1"}
]
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ defmodule ControlKeelWeb.ProtocolControllerTest do
assert %{"result" => %{"tools" => _}} = json_response(conn, 200)
end

test "DELETE without a session header yields 404", %{conn: conn} do
test "DELETE without a session header yields 404" do
conn = build_conn() |> delete("/mcp")
assert %{"error" => "session_not_found"} = json_response(conn, 404)
end
Expand Down
2 changes: 1 addition & 1 deletion test/controlkeel_web/live/new_session_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ defmodule ControlKeelWeb.NewSessionLiveTest do
{:ok, org_b} = Accounts.create_org_with_owner(user.id, %{name: "Param B", slug: "param-b"})
ws_a1 = workspace_fixture(%{org_id: org_a.id, name: "A One", slug: "a-one"})
ws_a2 = workspace_fixture(%{org_id: org_a.id, name: "A Two", slug: "a-two"})
ws_b = workspace_fixture(%{org_id: org_b.id, name: "Bee", slug: "bee"})
_ws_b = workspace_fixture(%{org_id: org_b.id, name: "Bee", slug: "bee"})

# No params: defaults — first admin org + its first workspace.
{:ok, view, html} = live(cloud_conn(user, nil), ~p"/new/session")
Expand Down
1 change: 0 additions & 1 deletion test/controlkeel_web/live/session_findings_live_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ defmodule ControlKeelWeb.SessionFindingsLiveTest do

alias ControlKeel.Accounts
alias ControlKeel.Mission
alias ControlKeel.Repo

test "session findings renders and copies a guided fix for supported findings", %{conn: conn} do
{org, ws, session} = org_bound_session_fixture()
Expand Down
1 change: 0 additions & 1 deletion test/controlkeel_web/live/session_release_live_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
defmodule ControlKeelWeb.SessionReleaseLiveTest do
use ControlKeelWeb.ConnCase, async: false

import ControlKeel.IntentFixtures
import Phoenix.LiveViewTest
import ControlKeel.MissionFixtures
import Ecto.Query
Expand Down
Loading