package cli import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" "github.com/darien/qcscrawl/internal/config" "github.com/darien/qcscrawl/internal/store" ) func TestDoctorDoesNotPrintToken(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { io.WriteString(w, `{"version":"1","appname":"test","details":{}}`) })) defer srv.Close() dir := t.TempDir() path := filepath.Join(dir, "config.toml") cfg := config.Default() cfg.BaseURL = srv.URL cfg.TokenEnv = "QCSCRAWL_TEST_TOKEN" cfg.DBPath = filepath.Join(dir, "archive.db") if err := config.Write(path, cfg); err != nil { t.Fatal(err) } const token = "do-not-print" t.Setenv(cfg.TokenEnv, token) var out strings.Builder err := Run(context.Background(), []string{"--config", path, "doctor", "--json"}, &out, io.Discard) if err != nil { t.Fatal(err) } if strings.Contains(out.String(), token) { t.Fatalf("token leaked: %s", out.String()) } var got map[string]any if err := json.Unmarshal([]byte(out.String()), &got); err != nil { t.Fatal(err) } if got["token_present"] != true || got["about_ok"] != true { t.Fatalf("result = %v", got) } _ = os.Getenv("PATH") } func TestStatusJSONWithoutConfigIsMachineReadable(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "missing.toml") var out strings.Builder if err := Run(context.Background(), []string{"--config", path, "status", "--json"}, &out, io.Discard); err != nil { t.Fatal(err) } var got map[string]any if err := json.Unmarshal([]byte(out.String()), &got); err != nil { t.Fatal(err) } if got["app_id"] != "qcscrawl" || (got["state"] != "uninitialized" && got["state"] != "ready") { t.Fatalf("status = %v", got) } } func TestOfflineJSONCommands(t *testing.T) { dir := t.TempDir() configPath := filepath.Join(dir, "config.toml") cfg := config.Default() cfg.BaseURL = "https://example.test" cfg.DBPath = filepath.Join(dir, "archive.db") if err := config.Write(configPath, cfg); err != nil { t.Fatal(err) } db, err := store.Open(cfg.DBPath) if err != nil { t.Fatal(err) } ctx := context.Background() if err = db.UpsertUserRaw(ctx, []byte(`{"id":7,"username":"alice"}`)); err != nil { t.Fatal(err) } if err = db.UpsertContentRaw(ctx, []byte(`{"id":2,"name":"page","text":"hello"}`)); err != nil { t.Fatal(err) } if err = db.UpsertMessageRaw(ctx, []byte(`{"id":1,"contentId":2,"createUserId":7,"createDate":"now","text":"hello world"}`)); err != nil { t.Fatal(err) } db.Close() for _, tc := range []struct { name string args []string want string }{ {"search", []string{"--config", configPath, "search", "hello", "--limit", "1", "--json"}, "array"}, {"messages", []string{"--config", configPath, "messages", "--content", "2", "--json"}, "array"}, {"content", []string{"--config", configPath, "content", "2", "--json"}, "object"}, {"users", []string{"--config", configPath, "users", "ali", "--json"}, "array"}, {"sql", []string{"--config", configPath, "sql", "SELECT count(*) AS n FROM messages", "--json"}, "array"}, } { t.Run(tc.name, func(t *testing.T) { var out strings.Builder if err := Run(ctx, tc.args, &out, io.Discard); err != nil { t.Fatal(err) } var v any if err := json.Unmarshal([]byte(out.String()), &v); err != nil { t.Fatal(err) } if tc.want == "array" { if _, ok := v.([]any); !ok { t.Fatalf("JSON = %T, want array", v) } } else if _, ok := v.(map[string]any); !ok { t.Fatalf("JSON = %T, want object", v) } }) } } func TestLeadingDoubleDashIsIgnored(t *testing.T) { var out strings.Builder if err := Run(context.Background(), []string{"--", "metadata", "--json"}, &out, io.Discard); err != nil { t.Fatal(err) } var got map[string]any if err := json.Unmarshal([]byte(out.String()), &got); err != nil || got["id"] != "qcscrawl" { t.Fatalf("metadata = %s, err=%v", out.String(), err) } } func TestHelpExplainsOfflineCommands(t *testing.T) { var out strings.Builder if err := Run(context.Background(), nil, &out, io.Discard); err != nil { t.Fatal(err) } if !strings.Contains(out.String(), "Offline archive commands") { t.Fatalf("empty-argument help = %q", out.String()) } out.Reset() if err := Run(context.Background(), []string{"--help"}, &out, io.Discard); err != nil { t.Fatal(err) } if text := out.String(); !strings.Contains(text, "Offline archive commands") || !strings.Contains(text, "--config PATH") { t.Fatalf("help = %q", text) } out.Reset() if err := Run(context.Background(), []string{"search", "--help"}, &out, io.Discard); err != nil { t.Fatal(err) } if !strings.Contains(out.String(), "No provider request") { t.Fatalf("search help = %q", out.String()) } }