package api import ( "encoding/base64" "path/filepath" "strings" "testing" "github.com/andydixon/nexus/internal/audit" "github.com/andydixon/nexus/internal/models" "github.com/andydixon/nexus/internal/storage" ) func TestSSHKeyPairPathsAcceptsSafeName(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) privPath, pubPath, err := sshKeyPairPaths("deploy_key-01") if err != nil { t.Fatalf("sshKeyPairPaths() error = %v", err) } wantPriv := filepath.Join(home, ".ssh", "deploy_key-01") if privPath != wantPriv { t.Fatalf("private path = %q, want %q", privPath, wantPriv) } if pubPath != wantPriv+".pub" { t.Fatalf("public path = %q, want %q", pubPath, wantPriv+".pub") } } func TestSSHKeyPairPathsRejectsTraversal(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) _, _, err := sshKeyPairPaths("../outside") if err == nil { t.Fatal("expected traversal key name to be rejected") } if !strings.Contains(err.Error(), "invalid characters") { t.Fatalf("error = %q, want invalid characters", err.Error()) } } func TestSSHKeyPairPathsRejectsNestedPath(t *testing.T) { home := t.TempDir() t.Setenv("HOME", home) _, _, err := sshKeyPairPaths("nested/key") if err == nil { t.Fatal("expected nested key path to be rejected") } } func TestRedactProfileSecretsClearsInlineSSHSecrets(t *testing.T) { profile := models.NewProfile(models.ProtocolSSH) profile.SSH.ProxyPassword = "proxy-secret" profile.SSH.TOTPSecret = "totp-secret" got := redactProfileSecrets([]*models.Profile{&profile}) if got[0].SSH.ProxyPassword != "" { t.Fatalf("ProxyPassword = %q, want empty", got[0].SSH.ProxyPassword) } if got[0].SSH.TOTPSecret != "" { t.Fatalf("TOTPSecret = %q, want empty", got[0].SSH.TOTPSecret) } if profile.SSH.ProxyPassword == "" || profile.SSH.TOTPSecret == "" { t.Fatal("redaction mutated the original profile") } } func TestCreateProfileRejectsUnsafeGitHubBaseURL(t *testing.T) { svc := newTestService(t) profile := models.NewProfile(models.ProtocolGitHub) profile.DisplayName = "Unsafe GitHub" profile.GitHub.BaseURL = "http://evil.example/api/v3" if _, err := svc.CreateProfile(profile); err == nil { t.Fatal("expected unsafe GitHub base URL to be rejected") } } func TestImportProfilesRejectsNullEntry(t *testing.T) { svc := newTestService(t) _, err := svc.ImportProfiles(`[null]`, "skip") if err == nil { t.Fatal("expected null import entry to be rejected") } if !strings.Contains(err.Error(), "entry 0 is null") { t.Fatalf("error = %q, want null entry detail", err.Error()) } } func TestImportProfilesRejectsOversizedJSON(t *testing.T) { svc := newTestService(t) oversized := strings.Repeat(" ", maxProfileImportJSONBytes+1) _, err := svc.ImportProfiles(oversized, "skip") if err == nil { t.Fatal("expected oversized import JSON to be rejected") } } func TestSFTPUploadDataRejectsOversizedPayloadBeforeSessionLookup(t *testing.T) { svc := newTestService(t) payload := base64.StdEncoding.EncodeToString(make([]byte, maxInlineSFTPUploadBytes+1)) err := svc.SFTPUploadData("missing-session", "/", "large.bin", payload) if err == nil { t.Fatal("expected oversized upload payload to be rejected") } if !strings.Contains(err.Error(), "too large") { t.Fatalf("error = %q, want too large", err.Error()) } } func newTestService(t *testing.T) *Service { t.Helper() store, err := storage.NewStore(filepath.Join(t.TempDir(), "test.db")) if err != nil { t.Fatalf("NewStore() error = %v", err) } t.Cleanup(func() { store.Close() }) return NewService(store, nil, nil, nil, audit.NewLogger(store), nil) }