test: replace legacy workspace deployment fixtures
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { execFileSync } from "node:child_process";
|
import { execFileSync } from "node:child_process";
|
||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { expect, test } from "vitest";
|
import { expect, test } from "vitest";
|
||||||
|
import { parseWorkspaceYaml } from "../src/workspaces/schema.js";
|
||||||
|
|
||||||
test("declares a durable isolated registry volume and only read-only Git credential mounts", () => {
|
test("declares a durable isolated registry volume and only read-only Git credential mounts", () => {
|
||||||
const compose = readFileSync(new URL("../../compose.yaml", import.meta.url), "utf8");
|
const compose = readFileSync(new URL("../../compose.yaml", import.meta.url), "utf8");
|
||||||
@@ -25,6 +26,120 @@ test("declares a durable isolated registry volume and only read-only Git credent
|
|||||||
expect(smoke).toContain('core_remote="/fixtures/offline.git"');
|
expect(smoke).toContain('core_remote="/fixtures/offline.git"');
|
||||||
expect(smoke).toContain('"degraded":true');
|
expect(smoke).toContain('"degraded":true');
|
||||||
expect(smoke).toContain('core_remote="/fixtures/remote.git"');
|
expect(smoke).toContain('core_remote="/fixtures/remote.git"');
|
||||||
|
expect(smoke).toContain(
|
||||||
|
'cp "$root/scripts/fixtures/workspace-registry-smoke.yaml" "$seed/workspaces/local.yaml"',
|
||||||
|
);
|
||||||
|
expect(smoke).not.toMatch(/npm\s+--prefix\s+[^\n]*backend[^\n]*\srun\s+build/);
|
||||||
|
expect(smoke).not.toMatch(/migrate-(?:legacy|v2-qdrant)/);
|
||||||
|
expect(smoke).toContain("<<'COMPOSE_YAML'");
|
||||||
|
expect(smoke).toContain('context: "${SMOKE_ROOT:?}"');
|
||||||
|
expect(smoke).toContain('image: "${SMOKE_IMAGE:?}"');
|
||||||
|
expect(smoke).toContain('THT_WORKSPACE_GIT_REMOTE: "${SMOKE_CORE_REMOTE:?}"');
|
||||||
|
expect(smoke).toContain('THT_WORKSPACE_GIT_BRANCH: "${SMOKE_BRANCH:?}"');
|
||||||
|
expect(smoke).toContain('source: "${SMOKE_REMOTE:?}"');
|
||||||
|
expect(smoke).toContain("type: bind");
|
||||||
|
expect(smoke).toContain("read_only: true");
|
||||||
|
expect(smoke).not.toContain("context: $root");
|
||||||
|
expect(smoke).not.toContain("image: $image");
|
||||||
|
expect(smoke).not.toContain("- $remote:/fixtures/remote.git:ro");
|
||||||
|
expect(smoke).toContain("compose-config-contract)");
|
||||||
|
expect(smoke).toContain("cleanup-failure-path)");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("shared workspace registry smoke fixture parses as schema v3 internal semantic identity", () => {
|
||||||
|
const source = readFileSync(
|
||||||
|
new URL("../../scripts/fixtures/workspace-registry-smoke.yaml", import.meta.url),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
const descriptor = parseWorkspaceYaml(source);
|
||||||
|
|
||||||
|
expect(descriptor).toMatchObject({
|
||||||
|
workspace: { schema_version: 3, id: "local", name: "Local" },
|
||||||
|
dwh: {
|
||||||
|
engine: "postgres",
|
||||||
|
database: "postgres",
|
||||||
|
schema: "public",
|
||||||
|
supported_transports: ["postgres_direct", "rest_api"],
|
||||||
|
},
|
||||||
|
semantic_index: {
|
||||||
|
vector_store: {
|
||||||
|
engine: "qdrant",
|
||||||
|
collection: "local",
|
||||||
|
dimensions: 1024,
|
||||||
|
distance: "cosine",
|
||||||
|
},
|
||||||
|
embedding: {
|
||||||
|
provider: "ollama_internal",
|
||||||
|
model: "qwen3-embedding:0.6b",
|
||||||
|
dimensions: 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
llm_policy: { default: "zai/glm-5.2", allowed: ["zai/glm-5.2"] },
|
||||||
|
diagnostics: {
|
||||||
|
dwh_rest: {
|
||||||
|
method: "GET",
|
||||||
|
path: "/health",
|
||||||
|
auth: "none",
|
||||||
|
response: { database: "database", schema: "schema" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(descriptor).not.toHaveProperty("evidence");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Windows clone contract copies the shared complete schema v3 descriptor", () => {
|
||||||
|
const fixture = readFileSync(
|
||||||
|
new URL("../../scripts/fixtures/workspace-registry-windows.yaml", import.meta.url),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
const descriptor = parseWorkspaceYaml(fixture);
|
||||||
|
const windows = readFileSync(
|
||||||
|
new URL("../../scripts/test-windows-clone-contract.ps1", import.meta.url),
|
||||||
|
"utf8",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(windows).toContain('"scripts/fixtures/workspace-registry-windows.yaml"');
|
||||||
|
expect(windows).toContain("Copy-Item -LiteralPath $workspaceFixture -Destination $workspaceDestination");
|
||||||
|
expect(windows).not.toContain("schema_version:");
|
||||||
|
expect(descriptor).toEqual({
|
||||||
|
workspace: {
|
||||||
|
schema_version: 3,
|
||||||
|
id: "task13-windows",
|
||||||
|
name: "Task 13 Windows",
|
||||||
|
language: "en",
|
||||||
|
},
|
||||||
|
dwh: {
|
||||||
|
engine: "postgres",
|
||||||
|
database: "warehouse",
|
||||||
|
schema: "public",
|
||||||
|
port: 5432,
|
||||||
|
timeout_ms: 5000,
|
||||||
|
supported_transports: ["postgres_direct", "rest_api"],
|
||||||
|
},
|
||||||
|
semantic_index: {
|
||||||
|
vector_store: {
|
||||||
|
engine: "qdrant",
|
||||||
|
collection: "task13-windows",
|
||||||
|
dimensions: 1024,
|
||||||
|
distance: "cosine",
|
||||||
|
},
|
||||||
|
embedding: {
|
||||||
|
provider: "ollama_internal",
|
||||||
|
model: "qwen3-embedding:0.6b",
|
||||||
|
dimensions: 1024,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
llm_policy: { default: "zai/glm-5.2", allowed: ["zai/glm-5.2"] },
|
||||||
|
diagnostics: {
|
||||||
|
dwh_rest: {
|
||||||
|
method: "GET",
|
||||||
|
path: "/health",
|
||||||
|
auth: "none",
|
||||||
|
response: { database: "database", schema: "schema" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(descriptor).not.toHaveProperty("evidence");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("workspace registry smoke image cleanup is scoped to the per-run image identity", () => {
|
test("workspace registry smoke image cleanup is scoped to the per-run image identity", () => {
|
||||||
@@ -37,6 +152,16 @@ test("workspace registry smoke image cleanup is scoped to the per-run image iden
|
|||||||
expect(output).toContain("workspace registry smoke image cleanup identity self-test passed");
|
expect(output).toContain("workspace registry smoke image cleanup identity self-test passed");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("workspace registry smoke cleanup failure-path self-test preserves status and retention", () => {
|
||||||
|
const output = execFileSync("bash", ["scripts/workspace-registry-smoke.sh"], {
|
||||||
|
cwd: new URL("../..", import.meta.url),
|
||||||
|
env: { ...process.env, WORKSPACE_REGISTRY_SMOKE_SELF_TEST: "cleanup-failure-path" },
|
||||||
|
encoding: "utf8",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(output).toContain("workspace registry smoke cleanup failure-path self-test passed");
|
||||||
|
});
|
||||||
|
|
||||||
test("workspace migration source modules are absent from the live backend boundary", () => {
|
test("workspace migration source modules are absent from the live backend boundary", () => {
|
||||||
expect(existsSync(new URL("../src/workspaces/migrate-legacy.ts", import.meta.url))).toBe(false);
|
expect(existsSync(new URL("../src/workspaces/migrate-legacy.ts", import.meta.url))).toBe(false);
|
||||||
expect(existsSync(new URL("../src/workspaces/migrate-v2-qdrant.ts", import.meta.url))).toBe(false);
|
expect(existsSync(new URL("../src/workspaces/migrate-v2-qdrant.ts", import.meta.url))).toBe(false);
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
workspace:
|
||||||
|
schema_version: 3
|
||||||
|
id: local
|
||||||
|
name: Local
|
||||||
|
description: Isolated workspace registry smoke fixture.
|
||||||
|
language: en
|
||||||
|
|
||||||
|
dwh:
|
||||||
|
engine: postgres
|
||||||
|
database: postgres
|
||||||
|
schema: public
|
||||||
|
port: 5432
|
||||||
|
timeout_ms: 5000
|
||||||
|
supported_transports:
|
||||||
|
- postgres_direct
|
||||||
|
- rest_api
|
||||||
|
|
||||||
|
semantic_index:
|
||||||
|
vector_store:
|
||||||
|
engine: qdrant
|
||||||
|
collection: local
|
||||||
|
dimensions: 1024
|
||||||
|
distance: cosine
|
||||||
|
embedding:
|
||||||
|
provider: ollama_internal
|
||||||
|
model: qwen3-embedding:0.6b
|
||||||
|
dimensions: 1024
|
||||||
|
|
||||||
|
llm_policy:
|
||||||
|
default: zai/glm-5.2
|
||||||
|
allowed:
|
||||||
|
- zai/glm-5.2
|
||||||
|
|
||||||
|
diagnostics:
|
||||||
|
dwh_rest:
|
||||||
|
method: GET
|
||||||
|
path: /health
|
||||||
|
auth: none
|
||||||
|
response:
|
||||||
|
database: database
|
||||||
|
schema: schema
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
workspace:
|
||||||
|
schema_version: 3
|
||||||
|
id: task13-windows
|
||||||
|
name: Task 13 Windows
|
||||||
|
language: en
|
||||||
|
|
||||||
|
dwh:
|
||||||
|
engine: postgres
|
||||||
|
database: warehouse
|
||||||
|
schema: public
|
||||||
|
port: 5432
|
||||||
|
timeout_ms: 5000
|
||||||
|
supported_transports:
|
||||||
|
- postgres_direct
|
||||||
|
- rest_api
|
||||||
|
|
||||||
|
semantic_index:
|
||||||
|
vector_store:
|
||||||
|
engine: qdrant
|
||||||
|
collection: task13-windows
|
||||||
|
dimensions: 1024
|
||||||
|
distance: cosine
|
||||||
|
embedding:
|
||||||
|
provider: ollama_internal
|
||||||
|
model: qwen3-embedding:0.6b
|
||||||
|
dimensions: 1024
|
||||||
|
|
||||||
|
llm_policy:
|
||||||
|
default: zai/glm-5.2
|
||||||
|
allowed:
|
||||||
|
- zai/glm-5.2
|
||||||
|
|
||||||
|
diagnostics:
|
||||||
|
dwh_rest:
|
||||||
|
method: GET
|
||||||
|
path: /health
|
||||||
|
auth: none
|
||||||
|
response:
|
||||||
|
database: database
|
||||||
|
schema: schema
|
||||||
@@ -9,7 +9,6 @@ trap 'rm -rf "$fixture"' EXIT HUP INT TERM
|
|||||||
new_fixture() {
|
new_fixture() {
|
||||||
rm -rf "$fixture/repository"
|
rm -rf "$fixture/repository"
|
||||||
mkdir -p \
|
mkdir -p \
|
||||||
"$fixture/repository/backend/src/workspaces" \
|
|
||||||
"$fixture/repository/deploy/env" \
|
"$fixture/repository/deploy/env" \
|
||||||
"$fixture/repository/deploy/workspaces" \
|
"$fixture/repository/deploy/workspaces" \
|
||||||
"$fixture/repository/docker/smoke" \
|
"$fixture/repository/docker/smoke" \
|
||||||
@@ -26,8 +25,6 @@ new_fixture() {
|
|||||||
printf '%s\n' 'THT_LLM_URL=https://llm.example.invalid' >"$fixture/repository/deploy/env/local.env.example"
|
printf '%s\n' 'THT_LLM_URL=https://llm.example.invalid' >"$fixture/repository/deploy/env/local.env.example"
|
||||||
printf '%s\n' '# generic launcher' >"$fixture/repository/scripts/run-stack.sh"
|
printf '%s\n' '# generic launcher' >"$fixture/repository/scripts/run-stack.sh"
|
||||||
printf '%s\n' '// generic frontend configuration' >"$fixture/repository/frontend/vite.config.ts"
|
printf '%s\n' '// generic frontend configuration' >"$fixture/repository/frontend/vite.config.ts"
|
||||||
printf '%s\n' '// explicit descriptor migration module may mention pgvector during conversion' \
|
|
||||||
>"$fixture/repository/backend/src/workspaces/migrate-legacy.ts"
|
|
||||||
printf '%s\n' 'language: en' 'vectors: { type: qdrant, base_url: http://qdrant:6333, collection: demo }' \
|
printf '%s\n' 'language: en' 'vectors: { type: qdrant, base_url: http://qdrant:6333, collection: demo }' \
|
||||||
>"$fixture/repository/deploy/workspaces/example.yaml"
|
>"$fixture/repository/deploy/workspaces/example.yaml"
|
||||||
printf '%s\n' '# qdrant backup helper' >"$fixture/repository/scripts/vector-backup.sh"
|
printf '%s\n' '# qdrant backup helper' >"$fixture/repository/scripts/vector-backup.sh"
|
||||||
|
|||||||
@@ -152,18 +152,9 @@ try {
|
|||||||
[System.IO.Directory]::CreateDirectory((Join-Path $seed "workspaces")) | Out-Null
|
[System.IO.Directory]::CreateDirectory((Join-Path $seed "workspaces")) | Out-Null
|
||||||
Invoke-BoundedNative -FilePath "git" -Arguments @("init", "--bare", "--initial-branch=main", $remote) -Label "initialize Windows bare registry" | Out-Null
|
Invoke-BoundedNative -FilePath "git" -Arguments @("init", "--bare", "--initial-branch=main", $remote) -Label "initialize Windows bare registry" | Out-Null
|
||||||
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "init", "--initial-branch=main") -Label "initialize Windows registry seed" | Out-Null
|
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "init", "--initial-branch=main") -Label "initialize Windows registry seed" | Out-Null
|
||||||
Write-Utf8File (Join-Path $seed "workspaces/task13-windows.yaml") @"
|
$workspaceFixture = Join-Path $spacedRepository "scripts/fixtures/workspace-registry-windows.yaml"
|
||||||
workspace:
|
$workspaceDestination = Join-Path $seed "workspaces/task13-windows.yaml"
|
||||||
schema_version: 2
|
Copy-Item -LiteralPath $workspaceFixture -Destination $workspaceDestination
|
||||||
id: task13-windows
|
|
||||||
name: Task 13 Windows
|
|
||||||
language: en
|
|
||||||
dwh:
|
|
||||||
engine: postgres
|
|
||||||
database: warehouse
|
|
||||||
schema: public
|
|
||||||
supported_transports: [postgres_direct]
|
|
||||||
"@
|
|
||||||
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "add", "workspaces/task13-windows.yaml") -Label "stage Windows registry seed" | Out-Null
|
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "add", "workspaces/task13-windows.yaml") -Label "stage Windows registry seed" | Out-Null
|
||||||
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "-c", "user.name=Task 13 Windows", "-c", "user.email=task13-windows@example.invalid", "commit", "-m", "Seed Windows smoke") -Label "commit Windows registry seed" | Out-Null
|
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "-c", "user.name=Task 13 Windows", "-c", "user.email=task13-windows@example.invalid", "commit", "-m", "Seed Windows smoke") -Label "commit Windows registry seed" | Out-Null
|
||||||
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "push", $remote, "HEAD:main") -Label "push Windows registry seed" | Out-Null
|
Invoke-BoundedNative -FilePath "git" -Arguments @("-C", $seed, "push", $remote, "HEAD:main") -Label "push Windows registry seed" | Out-Null
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ set -euo pipefail
|
|||||||
|
|
||||||
root="$(cd "$(dirname "$0")/.." && pwd -P)"
|
root="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke.XXXXXX")"
|
tmp="$(mktemp -d "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke.XXXXXX")"
|
||||||
|
tmp="$(cd "$tmp" && pwd -P)"
|
||||||
tmp_slug="$(basename "$tmp" | tr '[:upper:]._' '[:lower:]--' | tr -cd 'a-z0-9-')"
|
tmp_slug="$(basename "$tmp" | tr '[:upper:]._' '[:lower:]--' | tr -cd 'a-z0-9-')"
|
||||||
project="thoth-workspace-registry-smoke-${tmp_slug}-$$"
|
project="thoth-workspace-registry-smoke-${tmp_slug}-$$"
|
||||||
image="thothii-workspace-registry-smoke:${project}"
|
image="thothii-workspace-registry-smoke:${project}"
|
||||||
@@ -15,20 +16,128 @@ branch="workspace-registry-smoke"
|
|||||||
core_remote="/fixtures/remote.git"
|
core_remote="/fixtures/remote.git"
|
||||||
|
|
||||||
cleanup_smoke_image() {
|
cleanup_smoke_image() {
|
||||||
docker image rm -f "$image" >/dev/null 2>&1 || true
|
local inspect_status listed list_status
|
||||||
|
docker image inspect --format '{{.Id}}' "$image" >/dev/null 2>&1
|
||||||
|
inspect_status=$?
|
||||||
|
if [[ "$inspect_status" -eq 0 ]]; then
|
||||||
|
docker image rm -f "$image" >/dev/null 2>&1
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
listed="$(docker image ls --quiet --no-trunc "$image" 2>/dev/null)"
|
||||||
|
list_status=$?
|
||||||
|
[[ "$list_status" -eq 0 && -z "$listed" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace_registry_smoke_leftovers() {
|
workspace_registry_smoke_leftovers() {
|
||||||
{
|
local output status=0
|
||||||
docker ps -a --filter "label=com.docker.compose.project=$project" -q
|
if output="$(docker ps -a --filter "label=com.docker.compose.project=$project" -q)"; then
|
||||||
docker volume ls --filter "label=com.docker.compose.project=$project" -q
|
printf '%s\n' "$output"
|
||||||
docker network ls --filter "label=com.docker.compose.project=$project" -q
|
else
|
||||||
docker image inspect --format '{{.Id}}' "$image" 2>/dev/null || true
|
status=1
|
||||||
} | sed '/^$/d'
|
fi
|
||||||
|
if output="$(docker volume ls --filter "label=com.docker.compose.project=$project" -q)"; then
|
||||||
|
printf '%s\n' "$output"
|
||||||
|
else
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
if output="$(docker network ls --filter "label=com.docker.compose.project=$project" -q)"; then
|
||||||
|
printf '%s\n' "$output"
|
||||||
|
else
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
if output="$(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null)"; then
|
||||||
|
printf '%s\n' "$output"
|
||||||
|
elif output="$(docker image ls --quiet --no-trunc "$image" 2>/dev/null)"; then
|
||||||
|
printf '%s\n' "$output"
|
||||||
|
else
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
return "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
local body_status=$?
|
||||||
|
local down_status image_status enumeration_status rm_status=0 cleanup_incomplete=0 final_status
|
||||||
|
local leftovers
|
||||||
|
trap - EXIT HUP INT TERM
|
||||||
|
set +e
|
||||||
|
|
||||||
|
compose down --volumes --remove-orphans >/dev/null 2>&1
|
||||||
|
down_status=$?
|
||||||
|
cleanup_smoke_image
|
||||||
|
image_status=$?
|
||||||
|
leftovers="$(workspace_registry_smoke_leftovers)"
|
||||||
|
enumeration_status=$?
|
||||||
|
leftovers="$(printf '%s\n' "$leftovers" | sed '/^$/d')"
|
||||||
|
|
||||||
|
if [[ "$down_status" -ne 0 || "$image_status" -ne 0 || "$enumeration_status" -ne 0 || -n "$leftovers" ]]; then
|
||||||
|
cleanup_incomplete=1
|
||||||
|
else
|
||||||
|
rm -rf "$tmp"
|
||||||
|
rm_status=$?
|
||||||
|
[[ "$rm_status" -eq 0 ]] || cleanup_incomplete=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$cleanup_incomplete" -ne 0 ]]; then
|
||||||
|
echo "workspace registry cleanup incomplete: compose down status=$down_status, image cleanup status=$image_status, enumeration status=$enumeration_status, temp cleanup status=$rm_status." >&2
|
||||||
|
if [[ -n "$leftovers" ]]; then
|
||||||
|
echo "workspace registry cleanup left owned Docker resources:" >&2
|
||||||
|
printf '%s\n' "$leftovers" >&2
|
||||||
|
fi
|
||||||
|
echo "workspace registry smoke recovery path retained: $tmp" >&2
|
||||||
|
if [[ "$body_status" -ne 0 ]]; then
|
||||||
|
final_status=$body_status
|
||||||
|
else
|
||||||
|
final_status=1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "workspace registry cleanup proof: no compose containers, volumes, networks, image, or temporary path remain for $project."
|
||||||
|
final_status=$body_status
|
||||||
|
fi
|
||||||
|
exit "$final_status"
|
||||||
|
}
|
||||||
|
|
||||||
|
compose() {
|
||||||
|
SMOKE_ROOT="$root" \
|
||||||
|
SMOKE_IMAGE="$image" \
|
||||||
|
SMOKE_REMOTE="$remote" \
|
||||||
|
SMOKE_BRANCH="$branch" \
|
||||||
|
SMOKE_CORE_REMOTE="$core_remote" \
|
||||||
|
docker compose --project-name "$project" -f - "$@" <<'COMPOSE_YAML'
|
||||||
|
services:
|
||||||
|
core:
|
||||||
|
build:
|
||||||
|
context: "${SMOKE_ROOT:?}"
|
||||||
|
dockerfile: docker/core.Dockerfile
|
||||||
|
image: "${SMOKE_IMAGE:?}"
|
||||||
|
environment:
|
||||||
|
HOST: 0.0.0.0
|
||||||
|
PORT: "8787"
|
||||||
|
AUTH_MODE: none
|
||||||
|
THT_HARNESS_DIR: /app/harness
|
||||||
|
THT_BIN: /opt/venv/bin/tht
|
||||||
|
SETTINGS_FILE: /tmp/settings.json
|
||||||
|
THT_WORKSPACE_REGISTRY_ROOT: /data/workspace-registry
|
||||||
|
THT_WORKSPACE_GIT_REMOTE: "${SMOKE_CORE_REMOTE:?}"
|
||||||
|
THT_WORKSPACE_GIT_BRANCH: "${SMOKE_BRANCH:?}"
|
||||||
|
THT_WORKSPACE_INSTALLATION_ID: smoke
|
||||||
|
THT_WORKSPACE_SECRET_ROOTS: /run/secrets
|
||||||
|
volumes:
|
||||||
|
- type: volume
|
||||||
|
source: workspace-registry
|
||||||
|
target: /data/workspace-registry
|
||||||
|
- type: bind
|
||||||
|
source: "${SMOKE_REMOTE:?}"
|
||||||
|
target: /fixtures/remote.git
|
||||||
|
read_only: true
|
||||||
|
volumes:
|
||||||
|
workspace-registry: {}
|
||||||
|
COMPOSE_YAML
|
||||||
}
|
}
|
||||||
|
|
||||||
workspace_registry_smoke_self_test_image_cleanup_identity() {
|
workspace_registry_smoke_self_test_image_cleanup_identity() {
|
||||||
local calls exact_image foreign_project foreign_tag leftovers
|
local calls exact_image foreign_project foreign_tag leftovers removed=0
|
||||||
calls="$(mktemp "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke-image-contract.XXXXXX")"
|
calls="$(mktemp "${TMPDIR:-/tmp}/thoth-workspace-registry-smoke-image-contract.XXXXXX")"
|
||||||
project="thoth-workspace-registry-smoke-selftest-123"
|
project="thoth-workspace-registry-smoke-selftest-123"
|
||||||
exact_image="thothii-workspace-registry-smoke:${project}"
|
exact_image="thothii-workspace-registry-smoke:${project}"
|
||||||
@@ -39,19 +148,19 @@ workspace_registry_smoke_self_test_image_cleanup_identity() {
|
|||||||
docker() {
|
docker() {
|
||||||
printf '%s\n' "docker $*" >>"$calls"
|
printf '%s\n' "docker $*" >>"$calls"
|
||||||
case "$1 $2" in
|
case "$1 $2" in
|
||||||
"image rm")
|
|
||||||
[[ "$3" == "-f" ]] || return 41
|
|
||||||
[[ "$4" == "$exact_image" ]] || return 42
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
"image inspect")
|
"image inspect")
|
||||||
[[ "$3" == "--format" ]] || return 43
|
[[ "$3" == "--format" && "$5" == "$exact_image" ]] || return 43
|
||||||
[[ "$5" == "$exact_image" ]] || return 44
|
[[ "$removed" -eq 0 ]]
|
||||||
return 1
|
;;
|
||||||
|
"image rm")
|
||||||
|
[[ "$3" == "-f" && "$4" == "$exact_image" ]] || return 42
|
||||||
|
removed=1
|
||||||
|
;;
|
||||||
|
"image ls")
|
||||||
|
[[ "$3" == "--quiet" && "$4" == "--no-trunc" && "$5" == "$exact_image" ]] || return 47
|
||||||
;;
|
;;
|
||||||
"ps -a"|"volume ls"|"network ls")
|
"ps -a"|"volume ls"|"network ls")
|
||||||
[[ "$*" == *"label=com.docker.compose.project=$project"* ]] || return 45
|
[[ "$*" == *"label=com.docker.compose.project=$project"* ]] || return 45
|
||||||
return 0
|
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
return 46
|
return 46
|
||||||
@@ -61,7 +170,7 @@ workspace_registry_smoke_self_test_image_cleanup_identity() {
|
|||||||
|
|
||||||
cleanup_smoke_image
|
cleanup_smoke_image
|
||||||
leftovers="$(workspace_registry_smoke_leftovers)"
|
leftovers="$(workspace_registry_smoke_leftovers)"
|
||||||
[[ -z "$leftovers" ]] || {
|
[[ -z "$(printf '%s\n' "$leftovers" | sed '/^$/d')" ]] || {
|
||||||
echo "self-test observed leftovers for the per-run image" >&2
|
echo "self-test observed leftovers for the per-run image" >&2
|
||||||
printf '%s\n' "$leftovers" >&2
|
printf '%s\n' "$leftovers" >&2
|
||||||
return 1
|
return 1
|
||||||
@@ -72,62 +181,95 @@ workspace_registry_smoke_self_test_image_cleanup_identity() {
|
|||||||
echo "self-test cleanup touched a foreign workspace-registry smoke image reference" >&2
|
echo "self-test cleanup touched a foreign workspace-registry smoke image reference" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
rm -f "$calls"
|
||||||
echo "workspace registry smoke image cleanup identity self-test passed"
|
echo "workspace registry smoke image cleanup identity self-test passed"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [[ "${WORKSPACE_REGISTRY_SMOKE_SELF_TEST:-}" == "image-cleanup-identity" ]]; then
|
workspace_registry_smoke_self_test_compose_config() {
|
||||||
workspace_registry_smoke_self_test_image_cleanup_identity
|
local special_root rendered
|
||||||
exit 0
|
special_root="$tmp/compose config path # colon: fixture"
|
||||||
fi
|
root="$special_root/root context"
|
||||||
|
remote="$special_root/remote repo # fixture.git"
|
||||||
|
branch="workspace registry # branch"
|
||||||
|
core_remote="/fixtures/remote repo # fixture.git"
|
||||||
|
image="thothii-workspace-registry-smoke:compose-config-selftest"
|
||||||
|
project="thoth-workspace-registry-smoke-compose-config-selftest-$$"
|
||||||
|
rendered="$special_root/rendered.yaml"
|
||||||
|
mkdir -p "$root" "$remote"
|
||||||
|
|
||||||
cleanup() {
|
compose config --quiet
|
||||||
local cleanup_status=$?
|
compose config --format json >"$rendered"
|
||||||
compose down --volumes --remove-orphans >/dev/null 2>&1 || true
|
grep -Fq "$root" "$rendered"
|
||||||
cleanup_smoke_image
|
grep -Fq "$remote" "$rendered"
|
||||||
if [[ "$cleanup_status" -eq 0 ]]; then
|
grep -Fq "$branch" "$rendered"
|
||||||
local leftovers
|
grep -Fq "$core_remote" "$rendered"
|
||||||
leftovers="$(workspace_registry_smoke_leftovers)"
|
grep -Fq '"read_only": true' "$rendered"
|
||||||
if [[ -n "$leftovers" ]]; then
|
|
||||||
echo "workspace registry cleanup left owned Docker resources:" >&2
|
|
||||||
printf '%s\n' "$leftovers" >&2
|
|
||||||
cleanup_status=1
|
|
||||||
else
|
|
||||||
echo "workspace registry cleanup proof: no compose containers, volumes, networks, or image remain for $project."
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
rm -rf "$tmp"
|
rm -rf "$tmp"
|
||||||
exit "$cleanup_status"
|
echo "workspace registry smoke Compose config special-path self-test passed"
|
||||||
}
|
}
|
||||||
trap cleanup EXIT HUP INT TERM
|
|
||||||
|
|
||||||
compose() {
|
workspace_registry_smoke_self_test_cleanup_failure_path() {
|
||||||
docker compose --project-name "$project" -f - "$@" <<EOF
|
local fixture_root retained calls output status
|
||||||
services:
|
fixture_root="$tmp/cleanup failure # fixture"
|
||||||
core:
|
retained="$fixture_root/retained smoke path"
|
||||||
build:
|
calls="$fixture_root/calls.log"
|
||||||
context: $root
|
mkdir -p "$retained"
|
||||||
dockerfile: docker/core.Dockerfile
|
|
||||||
image: $image
|
set +e
|
||||||
environment:
|
output="$(
|
||||||
HOST: 0.0.0.0
|
(
|
||||||
PORT: "8787"
|
tmp="$retained"
|
||||||
AUTH_MODE: none
|
project="thoth-workspace-registry-smoke-cleanup-failure-selftest"
|
||||||
THT_HARNESS_DIR: /app/harness
|
image="thothii-workspace-registry-smoke:cleanup-failure-selftest"
|
||||||
THT_BIN: /opt/venv/bin/tht
|
compose() { printf '%s\n' "compose $*" >>"$calls"; return 71; }
|
||||||
SETTINGS_FILE: /tmp/settings.json
|
cleanup_smoke_image() { printf '%s\n' 'image cleanup' >>"$calls"; return 72; }
|
||||||
THT_WORKSPACE_REGISTRY_ROOT: /data/workspace-registry
|
workspace_registry_smoke_leftovers() { printf '%s\n' 'owned-resource-selftest'; printf '%s\n' 'enumerate leftovers' >>"$calls"; return 73; }
|
||||||
THT_WORKSPACE_GIT_REMOTE: $core_remote
|
trap cleanup EXIT
|
||||||
THT_WORKSPACE_GIT_BRANCH: $branch
|
exit 37
|
||||||
THT_WORKSPACE_INSTALLATION_ID: smoke
|
) 2>&1
|
||||||
THT_WORKSPACE_SECRET_ROOTS: /run/secrets
|
)"
|
||||||
volumes:
|
status=$?
|
||||||
- workspace-registry:/data/workspace-registry
|
set -e
|
||||||
- $remote:/fixtures/remote.git:ro
|
|
||||||
volumes:
|
[[ "$status" -eq 37 ]] || { echo "cleanup self-test did not preserve body status 37 (got $status)" >&2; return 1; }
|
||||||
workspace-registry: {}
|
grep -Fq 'compose down --volumes --remove-orphans' "$calls"
|
||||||
EOF
|
grep -Fq 'image cleanup' "$calls"
|
||||||
|
grep -Fq 'enumerate leftovers' "$calls"
|
||||||
|
grep -Fq 'cleanup incomplete: compose down status=71, image cleanup status=72, enumeration status=73' <<<"$output"
|
||||||
|
grep -Fq 'owned-resource-selftest' <<<"$output"
|
||||||
|
grep -Fq "recovery path retained: $retained" <<<"$output"
|
||||||
|
[[ -d "$retained" ]] || { echo "cleanup self-test did not retain its recovery path" >&2; return 1; }
|
||||||
|
rm -rf "$tmp"
|
||||||
|
echo "workspace registry smoke cleanup failure-path self-test passed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "${WORKSPACE_REGISTRY_SMOKE_SELF_TEST:-}" in
|
||||||
|
"") ;;
|
||||||
|
image-cleanup-identity)
|
||||||
|
workspace_registry_smoke_self_test_image_cleanup_identity
|
||||||
|
rm -rf "$tmp"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
compose-config-contract)
|
||||||
|
workspace_registry_smoke_self_test_compose_config
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
cleanup-failure-path)
|
||||||
|
workspace_registry_smoke_self_test_cleanup_failure_path
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown workspace registry smoke self-test: ${WORKSPACE_REGISTRY_SMOKE_SELF_TEST}" >&2
|
||||||
|
rm -rf "$tmp"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap 'exit 129' HUP
|
||||||
|
trap 'exit 130' INT
|
||||||
|
trap 'exit 143' TERM
|
||||||
|
|
||||||
wait_for_core() {
|
wait_for_core() {
|
||||||
local attempt
|
local attempt
|
||||||
for attempt in $(seq 1 30); do
|
for attempt in $(seq 1 30); do
|
||||||
@@ -149,9 +291,8 @@ echo "== Seed isolated workspace registry =="
|
|||||||
git init --bare --initial-branch=main "$remote" >/dev/null
|
git init --bare --initial-branch=main "$remote" >/dev/null
|
||||||
git clone "$remote" "$seed" >/dev/null
|
git clone "$remote" "$seed" >/dev/null
|
||||||
git -C "$seed" checkout -b "$branch" >/dev/null
|
git -C "$seed" checkout -b "$branch" >/dev/null
|
||||||
npm --prefix "$root/backend" run build >/dev/null
|
mkdir -p "$seed/workspaces"
|
||||||
node "$root/backend/dist/workspaces/migrate-legacy.js" \
|
cp "$root/scripts/fixtures/workspace-registry-smoke.yaml" "$seed/workspaces/local.yaml"
|
||||||
--input "$root/harness/workspaces/local.yaml" --output "$seed" --collection local >/dev/null
|
|
||||||
git -C "$seed" add workspaces/local.yaml
|
git -C "$seed" add workspaces/local.yaml
|
||||||
git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \
|
git -C "$seed" -c user.name='Workspace Registry Smoke' -c user.email='workspace-registry-smoke@example.invalid' \
|
||||||
commit -m 'Seed workspace registry smoke' >/dev/null
|
commit -m 'Seed workspace registry smoke' >/dev/null
|
||||||
|
|||||||
Reference in New Issue
Block a user