package app import ( "encoding/json" "errors" "os" "strings" "testing" "time" "GoNavi-Wails/internal/connection" "GoNavi-Wails/internal/secretstore" ) func TestBuildConnectionPackagePayloadIncludesSecretBundles(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() _, err := app.SaveConnection(connection.SavedConnectionInput{ ID: "conn-1", Name: "Primary", Config: connection.ConnectionConfig{ ID: "conn-1", Type: "postgres", Host: "db.local", Port: 5432, User: "postgres", Password: "db-secret", UseSSH: true, SSH: connection.SSHConfig{ Host: "jump.local", Port: 22, User: "ops", Password: "ssh-secret", }, URI: "postgres://postgres:db-secret@db.local/app", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } payload, err := app.buildConnectionPackagePayload() if err != nil { t.Fatalf("buildConnectionPackagePayload returned error: %v", err) } if _, parseErr := time.Parse(time.RFC3339, payload.ExportedAt); parseErr != nil { t.Fatalf("expected RFC3339 exportedAt, got %q", payload.ExportedAt) } if len(payload.Connections) != 1 { t.Fatalf("expected 1 connection in payload, got %d", len(payload.Connections)) } item := payload.Connections[0] if item.ID != "conn-1" { t.Fatalf("expected ID=conn-1, got %q", item.ID) } if item.Config.Password != "" { t.Fatalf("payload metadata must stay secretless, got password=%q", item.Config.Password) } if item.Config.SSH.Password != "" { t.Fatalf("payload metadata must stay secretless for SSH, got %q", item.Config.SSH.Password) } if item.Config.URI != "" { t.Fatalf("payload metadata must stay secretless for URI, got %q", item.Config.URI) } if item.Secrets.Password != "db-secret" { t.Fatalf("expected bundled primary password, got %q", item.Secrets.Password) } if item.Secrets.SSHPassword != "ssh-secret" { t.Fatalf("expected bundled SSH password, got %q", item.Secrets.SSHPassword) } if item.Secrets.OpaqueURI != "postgres://postgres:db-secret@db.local/app" { t.Fatalf("expected bundled URI secret, got %q", item.Secrets.OpaqueURI) } raw, err := json.Marshal(payload) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } if strings.Contains(string(raw), "secretRef") { t.Fatalf("payload must not contain secretRef, got %s", string(raw)) } } func TestBuildExportedConnectionPackageWithoutSecretsUsesV2AppManagedAndImportsWithoutPasswords(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() _, err := app.SaveConnection(connection.SavedConnectionInput{ ID: "conn-v2-no-secrets", Name: "Primary", Config: connection.ConnectionConfig{ ID: "conn-v2-no-secrets", Type: "postgres", Host: "db.local", Port: 5432, User: "postgres", Password: "db-secret", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } raw, err := app.buildExportedConnectionPackage(ConnectionExportOptions{ IncludeSecrets: false, FilePassword: "ignored-password", }) if err != nil { t.Fatalf("buildExportedConnectionPackage returned error: %v", err) } var file connectionPackageFileV2 if err := json.Unmarshal(raw, &file); err != nil { t.Fatalf("json.Unmarshal returned error: %v", err) } if file.V != connectionPackageSchemaVersionV2 { t.Fatalf("expected v2 package, got v=%d", file.V) } if file.P != connectionPackageProtectionAppManaged { t.Fatalf("expected app-managed protection, got p=%d", file.P) } if strings.Contains(string(raw), `"secrets"`) { t.Fatalf("expected exported JSON to omit secrets when IncludeSecrets=false, got %s", string(raw)) } importApp := NewAppWithSecretStore(newFakeAppSecretStore()) importApp.configDir = t.TempDir() imported, err := importApp.ImportConnectionsPayload(string(raw), "") if err != nil { t.Fatalf("ImportConnectionsPayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected 1 imported connection, got %d", len(imported)) } if imported[0].HasPrimaryPassword { t.Fatal("expected imported connection to keep empty password when secrets are excluded") } resolved, err := importApp.resolveConnectionSecrets(imported[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "" { t.Fatalf("expected imported password to be empty, got %q", resolved.Password) } } func TestImportConnectionPackagePayloadOverwritesExistingSecrets(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() _, err := app.SaveConnection(connection.SavedConnectionInput{ ID: "conn-1", Name: "Primary", Config: connection.ConnectionConfig{ ID: "conn-1", Type: "postgres", Host: "db.old.local", Port: 5432, User: "postgres", Password: "old-primary", UseSSH: true, SSH: connection.SSHConfig{ Host: "jump.old.local", Port: 22, User: "ops", Password: "old-ssh", }, URI: "postgres://old", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } imported, err := app.importConnectionPackagePayload(connectionPackagePayload{ Connections: []connectionPackageItem{ { ID: "conn-1", Name: "Imported", Config: connection.ConnectionConfig{ ID: "conn-1", Type: "postgres", Host: "db.new.local", Port: 5432, User: "postgres", UseSSH: true, SSH: connection.SSHConfig{ Host: "jump.new.local", Port: 22, User: "ops", }, }, Secrets: connectionSecretBundle{ Password: "new-primary", }, }, }, }) if err != nil { t.Fatalf("importConnectionPackagePayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected 1 imported item, got %d", len(imported)) } if imported[0].Name != "Imported" { t.Fatalf("expected imported name, got %q", imported[0].Name) } if !imported[0].HasPrimaryPassword { t.Fatal("expected primary password to be present after overwrite") } if imported[0].HasSSHPassword { t.Fatal("expected SSH password to be cleared by package overwrite") } if imported[0].HasOpaqueURI { t.Fatal("expected URI secret to be cleared by package overwrite") } resolved, err := app.resolveConnectionSecrets(imported[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "new-primary" { t.Fatalf("expected primary password to be overwritten, got %q", resolved.Password) } if resolved.SSH.Password != "" { t.Fatalf("expected SSH password to be cleared, got %q", resolved.SSH.Password) } if resolved.URI != "" { t.Fatalf("expected URI secret to be cleared, got %q", resolved.URI) } } func TestImportConnectionPackagePayloadLatestEntryWinsForSameID(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() imported, err := app.importConnectionPackagePayload(connectionPackagePayload{ Connections: []connectionPackageItem{ { ID: "conn-dup", Name: "First", Config: connection.ConnectionConfig{ ID: "conn-dup", Type: "postgres", Host: "db.local", Port: 5432, User: "postgres", }, Secrets: connectionSecretBundle{Password: "first-secret"}, }, { ID: "conn-dup", Name: "Second", Config: connection.ConnectionConfig{ ID: "conn-dup", Type: "postgres", Host: "db.local", Port: 5432, User: "postgres", }, Secrets: connectionSecretBundle{Password: "second-secret"}, }, }, }) if err != nil { t.Fatalf("importConnectionPackagePayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected duplicate ids to return 1 final imported item, got %d", len(imported)) } if imported[0].Name != "Second" { t.Fatalf("expected returned import result to keep latest entry, got %q", imported[0].Name) } saved, err := app.GetSavedConnections() if err != nil { t.Fatalf("GetSavedConnections returned error: %v", err) } if len(saved) != 1 { t.Fatalf("expected 1 saved item after duplicate id overwrite, got %d", len(saved)) } if saved[0].Name != "Second" { t.Fatalf("expected latest item to win, got %q", saved[0].Name) } resolved, err := app.resolveConnectionSecrets(saved[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "second-secret" { t.Fatalf("expected latest secret to win, got %q", resolved.Password) } } func TestImportConnectionsPayloadLegacyJSONRollsBackOnSaveFailure(t *testing.T) { withTestGOOS(t, "linux") failRef, err := secretstore.BuildRef(savedConnectionSecretKind, "legacy-2") if err != nil { t.Fatalf("BuildRef returned error: %v", err) } store := newFailOnPutSecretStore(failRef) app := NewAppWithSecretStore(store) app.configDir = t.TempDir() _, err = app.SaveConnection(connection.SavedConnectionInput{ ID: "legacy-1", Name: "Existing Legacy", Config: connection.ConnectionConfig{ ID: "legacy-1", Type: "postgres", Host: "db.old.local", Port: 5432, User: "postgres", Password: "old-primary", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } raw, err := json.Marshal([]connection.LegacySavedConnection{ { ID: "legacy-1", Name: "Imported Existing Legacy", Config: connection.ConnectionConfig{ ID: "legacy-1", Type: "postgres", Host: "db.new.local", Port: 5432, User: "postgres", }, }, { ID: "legacy-2", Name: "Imported New Legacy", Config: connection.ConnectionConfig{ ID: "legacy-2", Type: "mysql", Host: "db.second.local", Port: 3306, User: "root", Password: "second-primary", }, }, }) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } imported, err := app.ImportConnectionsPayload(string(raw), "ignored") if err != nil { t.Fatalf("expected ImportConnectionsPayload to succeed without secret store, got %v", err) } if len(imported) != 2 { t.Fatalf("expected 2 imported results, got %#v", imported) } saved, err := app.GetSavedConnections() if err != nil { t.Fatalf("GetSavedConnections returned error: %v", err) } if len(saved) != 2 { t.Fatalf("expected import to keep 2 legacy connections, got %d", len(saved)) } if saved[0].ID != "legacy-1" || saved[0].Name != "Imported Existing Legacy" { t.Fatalf("expected updated legacy metadata, got %#v", saved[0]) } if saved[0].Config.Host != "db.new.local" { t.Fatalf("expected import to update legacy host, got %q", saved[0].Config.Host) } resolved, err := app.resolveConnectionSecrets(saved[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "" { t.Fatalf("expected legacy import without password to clear stored password, got %q", resolved.Password) } if _, err := store.Get(failRef); !os.IsNotExist(err) { t.Fatalf("expected rollback to remove partially imported legacy secret ref, got err=%v", err) } } func TestImportLegacyConnectionsRollbackRemovesGeneratedSecretRefs(t *testing.T) { withTestGOOS(t, "linux") failRef, err := secretstore.BuildRef(savedConnectionSecretKind, "legacy-2") if err != nil { t.Fatalf("BuildRef returned error: %v", err) } store := newFailOnPutSecretStore(failRef) app := NewAppWithSecretStore(store) app.configDir = t.TempDir() imported, err := app.ImportLegacyConnections([]connection.LegacySavedConnection{ { Name: "Generated ID Legacy", Config: connection.ConnectionConfig{ Type: "postgres", Host: "db.generated.local", Port: 5432, User: "postgres", Password: "generated-secret", }, }, { ID: "legacy-2", Name: "Will Fail", Config: connection.ConnectionConfig{ ID: "legacy-2", Type: "mysql", Host: "db.fail.local", Port: 3306, User: "root", Password: "fail-secret", }, }, }) if err != nil { t.Fatalf("expected ImportLegacyConnections to succeed without secret store, got %v", err) } if len(imported) != 2 { t.Fatalf("expected 2 imported results after import, got %#v", imported) } saved, err := app.GetSavedConnections() if err != nil { t.Fatalf("GetSavedConnections returned error: %v", err) } if len(saved) != 2 { t.Fatalf("expected imported connections to be persisted, got %d saved connections", len(saved)) } if got := len(store.base.items); got != 0 { t.Fatalf("expected rollback to remove generated secret refs, got %d remaining items", got) } if _, err := store.Get(failRef); !os.IsNotExist(err) { t.Fatalf("expected rollback to remove failed explicit secret ref, got err=%v", err) } } func TestImportConnectionPackagePayloadRollsBackOnSaveFailure(t *testing.T) { withTestGOOS(t, "linux") failRef, err := secretstore.BuildRef(savedConnectionSecretKind, "conn-2") if err != nil { t.Fatalf("BuildRef returned error: %v", err) } store := newFailOnPutSecretStore(failRef) app := NewAppWithSecretStore(store) app.configDir = t.TempDir() _, err = app.SaveConnection(connection.SavedConnectionInput{ ID: "conn-1", Name: "Existing", Config: connection.ConnectionConfig{ ID: "conn-1", Type: "postgres", Host: "db.old.local", Port: 5432, User: "postgres", Password: "old-primary", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } imported, err := app.importConnectionPackagePayload(connectionPackagePayload{ Connections: []connectionPackageItem{ { ID: "conn-1", Name: "Imported Existing", Config: connection.ConnectionConfig{ ID: "conn-1", Type: "postgres", Host: "db.new.local", Port: 5432, User: "postgres", }, Secrets: connectionSecretBundle{Password: "new-primary"}, }, { ID: "conn-2", Name: "Imported New", Config: connection.ConnectionConfig{ ID: "conn-2", Type: "mysql", Host: "db.second.local", Port: 3306, User: "root", }, Secrets: connectionSecretBundle{Password: "second-primary"}, }, }, }) if err != nil { t.Fatalf("expected importConnectionPackagePayload to succeed without secret store, got %v", err) } if len(imported) != 2 { t.Fatalf("expected 2 imported results after import, got %#v", imported) } saved, err := app.GetSavedConnections() if err != nil { t.Fatalf("GetSavedConnections returned error: %v", err) } if len(saved) != 2 { t.Fatalf("expected import to keep 2 connections, got %d", len(saved)) } if saved[0].ID != "conn-1" || saved[0].Name != "Imported Existing" { t.Fatalf("expected imported connection metadata, got %#v", saved[0]) } if saved[0].Config.Host != "db.new.local" { t.Fatalf("expected import to update host, got %q", saved[0].Config.Host) } resolved, err := app.resolveConnectionSecrets(saved[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "new-primary" { t.Fatalf("expected import to update primary password, got %q", resolved.Password) } if _, err := store.Get(failRef); !os.IsNotExist(err) { t.Fatalf("expected rollback to remove partially imported secret ref, got err=%v", err) } } func TestImportConnectionsPayloadLegacyJSONClearsExistingSecretWhenMissing(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() _, err := app.SaveConnection(connection.SavedConnectionInput{ ID: "legacy-1", Name: "Legacy", Config: connection.ConnectionConfig{ ID: "legacy-1", Type: "postgres", Host: "db.local", Port: 5432, User: "postgres", Password: "legacy-secret", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } raw, err := json.Marshal([]connection.LegacySavedConnection{ { ID: "legacy-1", Name: "Legacy Updated", Config: connection.ConnectionConfig{ ID: "legacy-1", Type: "postgres", Host: "db.local", Port: 5432, User: "postgres", }, }, }) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } imported, err := app.ImportConnectionsPayload(string(raw), "ignored") if err != nil { t.Fatalf("ImportConnectionsPayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected 1 imported item, got %d", len(imported)) } if imported[0].Name != "Legacy Updated" { t.Fatalf("expected legacy metadata to be overwritten, got %q", imported[0].Name) } resolved, err := app.resolveConnectionSecrets(imported[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "" { t.Fatalf("expected legacy import to clear existing secret when the imported file omits it, got %q", resolved.Password) } } func TestImportConnectionsPayloadLegacyJSONLatestEntryWinsForSameID(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() raw, err := json.Marshal([]connection.LegacySavedConnection{ { ID: "legacy-dup", Name: "First", Config: connection.ConnectionConfig{ ID: "legacy-dup", Type: "postgres", Host: "db.first.local", Port: 5432, User: "postgres", Password: "first-secret", }, }, { ID: "legacy-dup", Name: "Second", Config: connection.ConnectionConfig{ ID: "legacy-dup", Type: "postgres", Host: "db.second.local", Port: 5432, User: "postgres", Password: "second-secret", }, }, }) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } imported, err := app.ImportConnectionsPayload(string(raw), "ignored") if err != nil { t.Fatalf("ImportConnectionsPayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected duplicate legacy ids to return 1 final imported item, got %d", len(imported)) } if imported[0].Name != "Second" { t.Fatalf("expected returned import result to keep latest legacy entry, got %q", imported[0].Name) } saved, err := app.GetSavedConnections() if err != nil { t.Fatalf("GetSavedConnections returned error: %v", err) } if len(saved) != 1 { t.Fatalf("expected 1 saved legacy item after duplicate id overwrite, got %d", len(saved)) } if saved[0].Name != "Second" { t.Fatalf("expected latest legacy item to win, got %q", saved[0].Name) } resolved, err := app.resolveConnectionSecrets(saved[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "second-secret" { t.Fatalf("expected latest legacy secret to win, got %q", resolved.Password) } } func TestImportConnectionsPayloadLegacyJSONLatestEntryWithoutPasswordDoesNotKeepEarlierDuplicateSecret(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() raw, err := json.Marshal([]connection.LegacySavedConnection{ { ID: "legacy-dup", Name: "First", Config: connection.ConnectionConfig{ ID: "legacy-dup", Type: "postgres", Host: "db.first.local", Port: 5432, User: "postgres", Password: "first-secret", }, }, { ID: "legacy-dup", Name: "Second", Config: connection.ConnectionConfig{ ID: "legacy-dup", Type: "postgres", Host: "db.second.local", Port: 5432, User: "postgres", }, }, }) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } imported, err := app.ImportConnectionsPayload(string(raw), "ignored") if err != nil { t.Fatalf("ImportConnectionsPayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected duplicate legacy ids to return 1 final imported item, got %d", len(imported)) } saved, err := app.GetSavedConnections() if err != nil { t.Fatalf("GetSavedConnections returned error: %v", err) } if len(saved) != 1 { t.Fatalf("expected 1 saved legacy item after duplicate id overwrite, got %d", len(saved)) } if saved[0].HasPrimaryPassword { t.Fatalf("expected latest legacy item without password to clear earlier duplicate secret, got view=%#v", saved[0]) } resolved, err := app.resolveConnectionSecrets(saved[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "" { t.Fatalf("expected latest legacy item without password to keep empty secret, got %q", resolved.Password) } } func TestImportConnectionsPayloadEnvelopeRejectsOversizedPayloadWithDedicatedError(t *testing.T) { raw, err := json.Marshal(connectionPackageFile{ SchemaVersion: connectionPackageSchemaVersion, Kind: connectionPackageKind, Cipher: connectionPackageCipher, KDF: connectionPackageKDFSpec{ Name: connectionPackageKDFName, MemoryKiB: connectionPackageKDFDefaultMemoryKiB, TimeCost: connectionPackageKDFDefaultTimeCost, Parallelism: connectionPackageKDFDefaultParallelism, Salt: "AAAAAAAAAAAAAAAAAAAAAA==", }, Nonce: "AAAAAAAAAAAAAAAA", Payload: strings.Repeat("A", connectionPackageMaxPayloadBase64Bytes+4), }) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() _, err = app.ImportConnectionsPayload(string(raw), "package-password") if !errors.Is(err, errConnectionPackagePayloadTooLarge) { t.Fatalf("expected errConnectionPackagePayloadTooLarge, got %v", err) } } func TestImportConnectionsPayloadEnvelopeRequiresPassword(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() raw := `{ "schemaVersion": 1, "kind": "gonavi_connection_package", "cipher": "AES-256-GCM", "kdf": { "name": "Argon2id", "memoryKiB": 65536, "timeCost": 3, "parallelism": 4, "salt": "salt" }, "nonce": "nonce", "payload": "payload" }` _, err := app.ImportConnectionsPayload(raw, "") if !errors.Is(err, errConnectionPackagePasswordRequired) { t.Fatalf("expected errConnectionPackagePasswordRequired, got %v", err) } } func TestImportConnectionsPayloadEnvelopeImportsAndOverwritesSecrets(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() _, err := app.SaveConnection(connection.SavedConnectionInput{ ID: "conn-1", Name: "Existing", Config: connection.ConnectionConfig{ ID: "conn-1", Type: "postgres", Host: "db.old.local", Port: 5432, User: "postgres", Password: "old-primary", UseSSH: true, SSH: connection.SSHConfig{ Host: "jump.old.local", Port: 22, User: "ops", Password: "old-ssh", }, URI: "postgres://old", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } file, err := encryptConnectionPackage(connectionPackagePayload{ Connections: []connectionPackageItem{ { ID: "conn-1", Name: "Imported", Config: connection.ConnectionConfig{ ID: "conn-1", Type: "postgres", Host: "db.new.local", Port: 5432, User: "postgres", }, Secrets: connectionSecretBundle{ Password: "new-primary", }, }, }, }, "package-password") if err != nil { t.Fatalf("encryptConnectionPackage returned error: %v", err) } raw, err := json.Marshal(file) if err != nil { t.Fatalf("json.Marshal returned error: %v", err) } imported, err := app.ImportConnectionsPayload(string(raw), "package-password") if err != nil { t.Fatalf("ImportConnectionsPayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected 1 imported item, got %d", len(imported)) } if imported[0].Name != "Imported" { t.Fatalf("expected imported name, got %q", imported[0].Name) } if !imported[0].HasPrimaryPassword { t.Fatal("expected primary password after envelope import") } if imported[0].HasSSHPassword { t.Fatal("expected missing SSH password in package to clear old secret") } if imported[0].HasOpaqueURI { t.Fatal("expected missing URI in package to clear old secret") } resolved, err := app.resolveConnectionSecrets(imported[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "new-primary" { t.Fatalf("expected primary password to be overwritten, got %q", resolved.Password) } if resolved.SSH.Password != "" { t.Fatalf("expected SSH password to be cleared, got %q", resolved.SSH.Password) } if resolved.URI != "" { t.Fatalf("expected URI secret to be cleared, got %q", resolved.URI) } } func TestBuildExportedConnectionPackageWithSecretsUsesV2AppManagedEncryption(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() saveConnectionForPackageExport(t, app, "conn-v2-app", "app-secret") raw, err := app.buildExportedConnectionPackage(ConnectionExportOptions{ IncludeSecrets: true, }) if err != nil { t.Fatalf("buildExportedConnectionPackage returned error: %v", err) } rawString := string(raw) if !isConnectionPackageV2AppManaged(rawString) { t.Fatalf("expected app-managed export, got %s", rawString) } for _, forbidden := range []string{ "app-secret", "schemaVersion", "cipher", "ENC:", } { if strings.Contains(rawString, forbidden) { t.Fatalf("v2 p=1 export must not contain %q: %s", forbidden, rawString) } } imported, err := app.ImportConnectionsPayload(rawString, "") if err != nil { t.Fatalf("ImportConnectionsPayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected 1 imported item, got %d", len(imported)) } resolved, err := app.resolveConnectionSecrets(imported[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "app-secret" { t.Fatalf("expected v2 p=1 import to restore password, got %q", resolved.Password) } } func TestBuildExportedConnectionPackageWithFilePasswordUsesV2ProtectedEnvelope(t *testing.T) { app := NewAppWithSecretStore(newFakeAppSecretStore()) app.configDir = t.TempDir() saveConnectionForPackageExport(t, app, "conn-v2-protected", "protected-secret") raw, err := app.buildExportedConnectionPackage(ConnectionExportOptions{ IncludeSecrets: true, FilePassword: "package-password", }) if err != nil { t.Fatalf("buildExportedConnectionPackage returned error: %v", err) } rawString := string(raw) if !isConnectionPackageV2Protected(rawString) { t.Fatalf("expected password-protected export, got %s", rawString) } if strings.Contains(rawString, "protected-secret") { t.Fatalf("v2 p=2 export must not contain plaintext secret: %s", rawString) } _, err = app.ImportConnectionsPayload(rawString, "wrong-password") if !errors.Is(err, errConnectionPackageDecryptFailed) { t.Fatalf("wrong v2 p=2 password should return unified error, got %v", err) } imported, err := app.ImportConnectionsPayload(rawString, "package-password") if err != nil { t.Fatalf("ImportConnectionsPayload returned error: %v", err) } if len(imported) != 1 { t.Fatalf("expected 1 imported item, got %d", len(imported)) } resolved, err := app.resolveConnectionSecrets(imported[0].Config) if err != nil { t.Fatalf("resolveConnectionSecrets returned error: %v", err) } if resolved.Password != "protected-secret" { t.Fatalf("expected v2 p=2 import to restore password, got %q", resolved.Password) } } func TestNormalizeConnectionPackageExportFilenameAddsExtension(t *testing.T) { filename := normalizeConnectionPackageExportFilename(`C:\tmp\connections`) if !strings.HasSuffix(filename, connectionPackageExtension) { t.Fatalf("expected filename to end with %q, got %q", connectionPackageExtension, filename) } alreadyExtended := normalizeConnectionPackageExportFilename(`C:\tmp\connections` + connectionPackageExtension) if alreadyExtended != `C:\tmp\connections`+connectionPackageExtension { t.Fatalf("expected existing extension to be preserved, got %q", alreadyExtended) } } type failOnPutSecretStore struct { base *fakeAppSecretStore failRef string } func newFailOnPutSecretStore(failRef string) *failOnPutSecretStore { return &failOnPutSecretStore{ base: newFakeAppSecretStore(), failRef: failRef, } } func saveConnectionForPackageExport(t *testing.T, app *App, id string, primaryPassword string) { t.Helper() _, err := app.SaveConnection(connection.SavedConnectionInput{ ID: id, Name: "Exported " + id, Config: connection.ConnectionConfig{ ID: id, Type: "postgres", Host: "db.local", Port: 5432, User: "postgres", Password: primaryPassword, UseSSH: true, SSH: connection.SSHConfig{ Host: "jump.local", Port: 22, User: "ops", Password: "ssh-" + primaryPassword, }, URI: "postgres://postgres:" + primaryPassword + "@db.local/app", }, }) if err != nil { t.Fatalf("SaveConnection returned error: %v", err) } } func (s *failOnPutSecretStore) Put(ref string, payload []byte) error { if ref == s.failRef { return errors.New("injected put failure") } return s.base.Put(ref, payload) } func (s *failOnPutSecretStore) Get(ref string) ([]byte, error) { return s.base.Get(ref) } func (s *failOnPutSecretStore) Delete(ref string) error { return s.base.Delete(ref) } func (s *failOnPutSecretStore) HealthCheck() error { return s.base.HealthCheck() }