package workspaceops import ( "context" "encoding/json" "io" "os" "path/filepath" "reflect" "strings" "testing" "github.com/aritmolab/thothii/tools/tht/internal/compose" "github.com/aritmolab/thothii/tools/tht/internal/config" ) type fakeRunner struct { run func(args []string, stdin string) (compose.Result, error) all [][]string stdins []string } func TestEvidenceConsolidationUsesClosedMaintenanceContract(t *testing.T) { request, err := Parse([]string{"evidence", "consolidate", "--workspace", "sales", "--json"}) if err != nil { t.Fatal(err) } if request.operatorCommand() != "evidence-consolidate" || !request.JSONMode() { t.Fatal("incorrect command") } envelope, err := request.stdinEnvelope() if err != nil || envelope.WorkspaceID != "sales" { t.Fatal("incorrect workspace envelope") } if _, err := Parse([]string{"evidence", "consolidate", "--workspace", "../foreign"}); err == nil { t.Fatal("accepted an unsafe workspace") } } func (r *fakeRunner) Run(_ context.Context, args []string, stdin io.Reader) (compose.Result, error) { payload := "" if stdin != nil { contents, err := io.ReadAll(stdin) if err != nil { return compose.Result{}, err } payload = string(contents) } r.all = append(r.all, append([]string(nil), args...)) r.stdins = append(r.stdins, payload) return r.run(args, payload) } func TestExecuteRunSendsOnlyTheOneShotEnvelope(t *testing.T) { installation := testInstallation(t) runner := &fakeRunner{run: func(args []string, stdin string) (compose.Result, error) { switch { case contains(args, "config", "--format", "json"): return compose.Result{Stdout: `{"services":{"core":{"image":"thothii-core:local"}}}`}, nil case reflect.DeepEqual(args, []string{"image", "inspect", "--format", "{{.Id}}", "thothii-core:local"}): return compose.Result{Stdout: "sha256:" + strings.Repeat("a", 64)}, nil case contains(args, "workspace-maintenance", "preprocess-run"): var envelope map[string]any if err := json.Unmarshal([]byte(stdin), &envelope); err != nil { t.Fatalf("stdin JSON = %q, err=%v", stdin, err) } if !reflect.DeepEqual(envelope, map[string]any{ "schemaVersion": float64(1), "workspaceId": "abc", }) { t.Fatalf("unexpected envelope: %#v", envelope) } return compose.Result{Stdout: successResult("preprocess run")}, nil default: t.Fatalf("unexpected Docker invocation: %#v", args) return compose.Result{}, nil } }} result, err := Execute( context.Background(), installation, runner, RunRequest{baseRequest: baseRequest{Workspace: "abc", JSON: true}}, ) if err != nil { t.Fatalf("Execute() error = %v", err) } if result.Status != "succeeded" || result.Operation != "preprocess run" { t.Fatalf("unexpected result: %#v", result) } } func TestParseAcceptsOnlyInspectAndCompletePreprocessing(t *testing.T) { for _, args := range [][]string{ {"inspect", "--workspace", "abc", "--json"}, {"preprocess", "run", "--workspace", "abc", "--json"}, {"preprocess", "clear", "--workspace", "abc", "--json"}, } { if _, err := Parse(args); err != nil { t.Fatalf("Parse(%v) error = %v", args, err) } } retired := [][]string{ {"preprocess", "dwh", "--workspace", "abc"}, {"preprocess", "evidence", "--workspace", "abc"}, {"preprocess", "run", "--workspace", "abc", "--resume", strings.Repeat("a", 32)}, {"preprocess", "run", "--workspace", "abc", "--dry-run"}, {"schema", "suggest-fks", "--workspace", "abc"}, {"schema", "check", "--workspace", "abc"}, {"schema", "accept", "--workspace", "abc"}, {"index-schema", "--workspace", "abc"}, {"vector", "inspect", "--workspace", "abc"}, {"vector", "rebuild", "--workspace", "abc"}, } for _, args := range retired { if _, err := Parse(args); err == nil { t.Fatalf("retired command accepted: %v", args) } } } func testInstallation(t *testing.T) config.Installation { t.Helper() temporaryRoot, err := filepath.EvalSymlinks(os.TempDir()) if err != nil { t.Fatal(err) } root, err := os.MkdirTemp(temporaryRoot, "tht-workspaceops-") if err != nil { t.Fatal(err) } t.Cleanup(func() { _ = os.RemoveAll(root) }) project := filepath.Join(root, "project") if err := os.MkdirAll(filepath.Join(project, "deploy"), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(project, "compose.yaml"), []byte("services: {}\n"), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(project, "deploy", "compose.local.yaml"), []byte("services: {}\n"), 0o600); err != nil { t.Fatal(err) } envFile := filepath.Join(root, "installation.env") if err := os.WriteFile(envFile, []byte("SAFE_VALUE=1\n"), 0o600); err != nil { t.Fatal(err) } return config.Installation{ Path: filepath.Join(root, "thothii-installation.yaml"), Profile: "local", ProjectDirectory: project, EnvFile: envFile, } } func contains(values []string, sequence ...string) bool { for start := range values { if start+len(sequence) <= len(values) && reflect.DeepEqual(values[start:start+len(sequence)], sequence) { return true } } return false } func successResult(operation string) string { return `{"schemaVersion":1,"status":"succeeded","code":"ok","workspaceId":"abc","workspaceRevision":"1234567890abcdef1234567890abcdef12345678","descriptorBlob":"sha256:` + strings.Repeat("f", 64) + `","operation":"` + operation + `","completedStages":[]}` } func TestEvidenceSourceCommandsAreClosedAndCarryReviewIdentity(t *testing.T) { request, err := Parse([]string{"evidence", "refresh", "--workspace", "sales", "--json"}) if err != nil || request.operatorCommand() != "evidence-refresh" { t.Fatalf("refresh: %v", err) } args := []string{"evidence", "decide", "--workspace", "sales", "--source-id", strings.Repeat("a", 64), "--revision", strings.Repeat("b", 64), "--decision", "keep"} request, err = Parse(args) if err != nil { t.Fatal(err) } envelope, _ := request.stdinEnvelope() if envelope.SourceID != strings.Repeat("a", 64) || envelope.Decision != "keep" || request.operatorCommand() != "evidence-decide" { t.Fatal("review identity lost") } for _, invalid := range [][]string{ {"evidence", "refresh", "--workspace", "sales", "--url", "http://localhost"}, {"evidence", "decide", "--workspace", "sales"}, append(append([]string{}, args...), "--decision", "replace"), {"evidence", "consolidate", "--workspace", "sales", "--source-id", strings.Repeat("a", 64)}, } { if _, err := Parse(invalid); err == nil { t.Fatalf("accepted invalid flags: %v", invalid) } } }