Skip to content
Closed
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
13 changes: 4 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Docs: .gitignore.doc.md

__pycache__/
__v0_devtools.tsx
Expand All @@ -12,6 +11,9 @@ __v0_runtime_loader.js
.next/
.pytest_cache/
.ruff_cache/
.sin_memory.db
.sin-code/
.sin/
.snowflake/
.tdd-locks/
.v0-trash/
Expand Down Expand Up @@ -56,11 +58,4 @@ SIN-Code-SCA-Tool/
SIN-Code-Security-Bundle/
sin-code.exe
sin-tui
# Runtime SQLite / index artifacts produced by the Go binary at runtime.
# These live under cmd/ (not under ~/.local/share/sin-code/) because the
# store open() uses a relative path joined against os.Getwd(). Follow-up
# issue #62 will move them to os.UserConfigDir(); until then we keep them
# out of the index.
cmd/sin-code/internal/.sin-code/
cmd/sin-code/tui/.sin-code/
.venv-forge-test/
*.cov.out
6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/all_cov.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/cold_cov.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/combo2_cov.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/combo_cov.out

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cmd/sin-code/cov_all.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mode: set
6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/cov_small.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/final2_cov.out

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cmd/sin-code/final3_cov.out
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mode: set
6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/final4_cov.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/final_cov.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/full2_cov.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/full_cov.out

Large diffs are not rendered by default.

6,656 changes: 6,656 additions & 0 deletions cmd/sin-code/grasp_cov.out

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions cmd/sin-code/internal/adw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,31 @@ func TestCheckTODOs(t *testing.T) {
}
}

func TestCheckTODOs_SkipsAdwTestFile(t *testing.T) {
issues := checkTODOs("internal/adw_test.go", "// TODO: should be ignored\n")
if len(issues) != 0 {
t.Errorf("expected 0 issues for adw_test.go, got %d", len(issues))
}
}

func TestCheckTODOs_SkipsQuotedString(t *testing.T) {
content := `package main
var msg = "TODO: not a real todo"
`
issues := checkTODOs("main.go", content)
if len(issues) != 0 {
t.Errorf("expected 0 issues for quoted string, got %d", len(issues))
}
}

func TestCheckTODOs_SkipsRawString(t *testing.T) {
content := "package main\nvar hint = `TODO: inside\ncontinues\n"
issues := checkTODOs("main.go", content)
if len(issues) != 0 {
t.Errorf("expected 0 issues for raw string start, got %d", len(issues))
}
}

func TestFindCircularDeps(t *testing.T) {
imports := map[string][]string{
"a.go": {"b.go"},
Expand Down
150 changes: 150 additions & 0 deletions cmd/sin-code/internal/agent_cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-License-Identifier: MIT
// Purpose: Unit tests for agent subcommand helpers. (st-cov1)
package internal

import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
)

func TestFetchModels_HappyPath(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"data":[{"id":"gpt-4"},{"id":"gpt-3.5"}]}`))
}))
defer srv.Close()

models, err := fetchModels(srv.URL, "")
if err != nil {
t.Fatalf("fetchModels failed: %v", err)
}
if len(models) != 2 || models[0] != "gpt-4" {
t.Errorf("fetchModels = %v, want [gpt-4 gpt-3.5]", models)
}
}

func TestFetchModels_ErrorStatus(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusUnauthorized)
}))
defer srv.Close()

if _, err := fetchModels(srv.URL, ""); err == nil {
t.Fatal("expected error for non-200 status")
}
}

func TestOpenAgentInEditor_SeedsAndOpens(t *testing.T) {
oldEditor := os.Getenv("EDITOR")
t.Setenv("EDITOR", "true")
defer os.Setenv("EDITOR", oldEditor)

dir := t.TempDir()
t.Setenv("SIN_CODE_CONFIG_DIR", dir)

if err := openAgentInEditor("test-agent"); err != nil {
t.Fatalf("openAgentInEditor failed: %v", err)
}

cfgPath := filepath.Join(dir, "sin-code", "agents", "test-agent", "agent.toml")
if _, err := os.Stat(cfgPath); err != nil {
t.Errorf("expected config file at %s: %v", cfgPath, err)
}
}

func TestOpenAgentInEditor_MissingEditorFails(t *testing.T) {
oldEditor := os.Getenv("EDITOR")
t.Setenv("EDITOR", "nonexistent-binary-for-test-xyz")
defer os.Setenv("EDITOR", oldEditor)

dir := t.TempDir()
t.Setenv("SIN_CODE_CONFIG_DIR", dir)

if err := openAgentInEditor("another-agent"); err == nil {
t.Fatal("expected error for missing editor binary")
}
}

func TestOpenAgentInEditor_DefaultEditor(t *testing.T) {
oldEditor := os.Getenv("EDITOR")
t.Setenv("EDITOR", "")
defer os.Setenv("EDITOR", oldEditor)

binDir := t.TempDir()
fakeVim := filepath.Join(binDir, "vim")
if err := os.WriteFile(fakeVim, []byte("#!/bin/sh\nexit 0\n"), 0o755); err != nil {
t.Fatalf("write fake vim: %v", err)
}
oldPath := os.Getenv("PATH")
t.Setenv("PATH", binDir)
defer os.Setenv("PATH", oldPath)

dir := t.TempDir()
t.Setenv("SIN_CODE_CONFIG_DIR", dir)

if err := openAgentInEditor("default-editor-agent"); err != nil {
t.Fatalf("openAgentInEditor with default editor failed: %v", err)
}
}

func TestOpenAgentInEditor_InvalidName(t *testing.T) {
if err := openAgentInEditor("invalid/name"); err == nil {
t.Fatal("expected error for invalid agent name")
}
}

func TestApplyAgentEdits_InvalidName(t *testing.T) {
if err := applyAgentEdits("invalid/name", []string{"model=gpt-4"}); err == nil {
t.Fatal("expected error for invalid agent name")
}
}

func TestOpenAgentInEditor_MkdirError(t *testing.T) {
oldEditor := os.Getenv("EDITOR")
t.Setenv("EDITOR", "true")
defer os.Setenv("EDITOR", oldEditor)

dir := t.TempDir()
readOnly := filepath.Join(dir, "readonly")
if err := os.Mkdir(readOnly, 0o555); err != nil {
t.Fatalf("mkdir readonly: %v", err)
}
t.Setenv("SIN_CODE_CONFIG_DIR", readOnly)

if err := openAgentInEditor("mkdir-error-agent"); err == nil {
t.Fatal("expected error when mkdir fails")
}
}

func TestApplyAgentEdits_MkdirError(t *testing.T) {
dir := t.TempDir()
readOnly := filepath.Join(dir, "readonly")
if err := os.Mkdir(readOnly, 0o555); err != nil {
t.Fatalf("mkdir readonly: %v", err)
}
t.Setenv("SIN_CODE_CONFIG_DIR", readOnly)

if err := applyAgentEdits("mkdir-error-agent", []string{"model=gpt-4"}); err == nil {
t.Fatal("expected error when mkdir fails")
}
}

func TestApplyAgentEdits_CreateError(t *testing.T) {
dir := t.TempDir()
t.Setenv("SIN_CODE_CONFIG_DIR", dir)
agentDirPath := filepath.Join(dir, "sin-code", "agents", "create-error-agent")
if err := os.MkdirAll(agentDirPath, 0o755); err != nil {
t.Fatalf("mkdir agent dir: %v", err)
}
// Create agent.toml as a directory so os.Create fails.
if err := os.Mkdir(filepath.Join(agentDirPath, "agent.toml"), 0o755); err != nil {
t.Fatalf("mkdir agent.toml: %v", err)
}

if err := applyAgentEdits("create-error-agent", []string{"model=gpt-4"}); err == nil {
t.Fatal("expected error when create fails")
}
}
173 changes: 173 additions & 0 deletions cmd/sin-code/internal/agent_doctor_cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: MIT
// Purpose: Unit tests for agent-show and agent-doctor commands. (st-cov1)
package internal

import (
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"

"github.com/spf13/cobra"

"github.com/OpenSIN-Code/SIN-Code-Bundle/cmd/sin-code/internal/orchestrator"
)

func captureAgentCmd(t *testing.T, cmd *cobra.Command, args []string) (string, error) {
t.Helper()
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w

err := cmd.RunE(cmd, args)
w.Close()
os.Stdout = old
out, _ := io.ReadAll(r)
return string(out), err
}

func withIsolatedAgentConfig(t *testing.T) {
t.Helper()
old := os.Getenv("SIN_CODE_CONFIG_DIR")
os.Setenv("SIN_CODE_CONFIG_DIR", t.TempDir())
t.Cleanup(func() { os.Setenv("SIN_CODE_CONFIG_DIR", old) })
}

func TestAgentShow(t *testing.T) {
withIsolatedAgentConfig(t)
oldFormat := orch2Format
orch2Format = "text"
defer func() { orch2Format = oldFormat }()

out, err := captureAgentCmd(t, OrchestratorAgentShowCmd, []string{"coder"})
if err != nil {
t.Fatalf("agent-show failed: %v", err)
}
if !strings.Contains(out, "Agent coder") {
t.Errorf("expected agent show output, got %q", out)
}
}

func TestAgentShowJSON(t *testing.T) {
withIsolatedAgentConfig(t)
oldFormat := orch2Format
orch2Format = "json"
defer func() { orch2Format = oldFormat }()

out, err := captureAgentCmd(t, OrchestratorAgentShowCmd, []string{"coder"})
if err != nil {
t.Fatalf("agent-show json failed: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal([]byte(out), &parsed); err != nil {
t.Fatalf("agent-show json not valid: %v\n%q", err, out)
}
}

func TestAgentDoctorOffline(t *testing.T) {
withIsolatedAgentConfig(t)
oldOffline := agDoctorOffline
oldFormat := orch2Format
agDoctorOffline = true
orch2Format = "text"
defer func() {
agDoctorOffline = oldOffline
orch2Format = oldFormat
}()

out, err := captureAgentCmd(t, OrchestratorAgentDoctorCmd, []string{})
if err == nil {
t.Fatal("agent-doctor expected error for default agents with missing keys")
}
if !strings.Contains(out, "Doctor") {
t.Errorf("expected doctor output, got %q", out)
}
}

func TestAgentDoctorFiltered(t *testing.T) {
withIsolatedAgentConfig(t)
oldOffline := agDoctorOffline
oldFormat := orch2Format
agDoctorOffline = true
orch2Format = "text"
defer func() {
agDoctorOffline = oldOffline
orch2Format = oldFormat
}()

out, err := captureAgentCmd(t, OrchestratorAgentDoctorCmd, []string{"coder"})
if err == nil {
t.Fatal("agent-doctor expected error for coder with missing keys")
}
if !strings.Contains(out, "coder") {
t.Errorf("expected filtered agent output, got %q", out)
}
}

func TestAgentDoctorWithMockServer(t *testing.T) {
oldKey := os.Getenv("OPENAI_API_KEY")
os.Setenv("OPENAI_API_KEY", "test-key")
defer func() { os.Setenv("OPENAI_API_KEY", oldKey) }()

oldFormat := orch2Format
orch2Format = "json"
defer func() { orch2Format = oldFormat }()

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]string{{"id": "test-model"}},
})
}))
defer srv.Close()

// Create a custom agent with baseURL pointing to mock server
cfg := orchestrator.AgentConfig{
Name: "mock-agent",
Provider: "openai",
BaseURL: srv.URL,
Model: "test-model",
}
rep := runDoctor([]orchestrator.AgentConfig{cfg}, false)
if len(rep) != 1 {
t.Fatalf("expected 1 report, got %d", len(rep))
}
if !rep[0].OK {
t.Errorf("expected OK report, got issues %v", rep[0].Issues)
}
}

func TestAgentDoctorUnknownProvider(t *testing.T) {
cfg := orchestrator.AgentConfig{Name: "bad", Provider: "unknown-provider"}
rep := runDoctor([]orchestrator.AgentConfig{cfg}, true)
if len(rep) != 1 || rep[0].OK {
t.Fatalf("expected failing report for unknown provider, got %+v", rep[0])
}
}

func TestAgentDoctorMissingBaseURL(t *testing.T) {
cfg := orchestrator.AgentConfig{Name: "nourl", Provider: "nim"}
rep := runDoctor([]orchestrator.AgentConfig{cfg}, true)
if len(rep) != 1 || rep[0].OK {
t.Fatalf("expected failing report for missing base URL, got %+v", rep[0])
}
}

func TestFetchModels(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]string{{"id": "model-a"}, {"id": "model-b"}},
})
}))
defer srv.Close()

models, err := fetchModels(srv.URL, "")
if err != nil {
t.Fatalf("fetchModels failed: %v", err)
}
if len(models) != 2 {
t.Errorf("expected 2 models, got %v", models)
}
}
Loading
Loading