mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-25 07:53:44 +08:00
448 lines
12 KiB
Go
448 lines
12 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"GoNavi-Wails/internal/connection"
|
|
"GoNavi-Wails/internal/secretstore"
|
|
)
|
|
|
|
func TestSavedQueryRepositorySaveUpdateAndDelete(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
query := connection.SavedQuery{
|
|
ID: "saved-1",
|
|
Name: "Orders",
|
|
SQL: "select * from orders",
|
|
ConnectionID: "conn-1",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
}
|
|
if _, err := app.SaveQuery(query); err != nil {
|
|
t.Fatalf("SaveQuery returned error: %v", err)
|
|
}
|
|
|
|
saved, err := app.GetSavedQueries()
|
|
if err != nil {
|
|
t.Fatalf("GetSavedQueries returned error: %v", err)
|
|
}
|
|
if len(saved) != 1 || saved[0].Name != "Orders" {
|
|
t.Fatalf("expected saved query to be persisted, got %#v", saved)
|
|
}
|
|
|
|
query.Name = "Orders Updated"
|
|
query.SQL = "select id from orders"
|
|
if _, err := app.SaveQuery(query); err != nil {
|
|
t.Fatalf("SaveQuery update returned error: %v", err)
|
|
}
|
|
saved, err = app.GetSavedQueries()
|
|
if err != nil {
|
|
t.Fatalf("GetSavedQueries after update returned error: %v", err)
|
|
}
|
|
if len(saved) != 1 || saved[0].Name != "Orders Updated" || saved[0].SQL != "select id from orders" {
|
|
t.Fatalf("expected saved query to be updated in place, got %#v", saved)
|
|
}
|
|
|
|
if err := app.DeleteQuery("saved-1"); err != nil {
|
|
t.Fatalf("DeleteQuery returned error: %v", err)
|
|
}
|
|
saved, err = app.GetSavedQueries()
|
|
if err != nil {
|
|
t.Fatalf("GetSavedQueries after delete returned error: %v", err)
|
|
}
|
|
if len(saved) != 0 {
|
|
t.Fatalf("expected saved query to be deleted, got %#v", saved)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(app.configDir, savedQueriesFileName)); err != nil {
|
|
t.Fatalf("expected saved query file to remain readable after delete: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestImportSavedQueriesUpsertsAndSkipsInvalidItems(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
if _, err := app.SaveQuery(connection.SavedQuery{
|
|
ID: "saved-1",
|
|
Name: "Old",
|
|
SQL: "select 1",
|
|
ConnectionID: "conn-1",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
}); err != nil {
|
|
t.Fatalf("seed SaveQuery returned error: %v", err)
|
|
}
|
|
|
|
imported, err := app.ImportSavedQueries(connection.SavedQueryImportPayload{
|
|
Queries: []connection.SavedQuery{
|
|
{
|
|
ID: "saved-1",
|
|
Name: "New",
|
|
SQL: "select 2",
|
|
ConnectionID: "conn-1",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
},
|
|
{
|
|
ID: "saved-2",
|
|
Name: "Second",
|
|
SQL: "select 3",
|
|
ConnectionID: "conn-2",
|
|
DBName: "analytics",
|
|
CreatedAt: 200,
|
|
},
|
|
{
|
|
ID: "invalid",
|
|
Name: "Missing SQL",
|
|
ConnectionID: "conn-3",
|
|
DBName: "app",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ImportSavedQueries returned error: %v", err)
|
|
}
|
|
if len(imported) != 2 {
|
|
t.Fatalf("expected 2 valid saved queries after import, got %#v", imported)
|
|
}
|
|
if imported[0].Name != "New" || imported[0].SQL != "select 2" {
|
|
t.Fatalf("expected import to upsert saved-1, got %#v", imported[0])
|
|
}
|
|
if imported[1].ID != "saved-2" {
|
|
t.Fatalf("expected import to append saved-2, got %#v", imported[1])
|
|
}
|
|
}
|
|
|
|
func TestSavedQueryRepositoryPreservesSQLWhitespace(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
query := connection.SavedQuery{
|
|
ID: "saved-whitespace",
|
|
Name: "Formatted",
|
|
SQL: "\n select * from orders;\n",
|
|
ConnectionID: "conn-1",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
}
|
|
persisted, err := app.SaveQuery(query)
|
|
if err != nil {
|
|
t.Fatalf("SaveQuery returned error: %v", err)
|
|
}
|
|
if persisted.SQL != query.SQL {
|
|
t.Fatalf("expected saved SQL whitespace to be preserved, got %q", persisted.SQL)
|
|
}
|
|
}
|
|
|
|
func TestSavedQueryRepositoryLocalizesGeneratedName(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
app.SetLanguage("en-US")
|
|
|
|
persisted, err := app.SaveQuery(connection.SavedQuery{
|
|
ID: "saved-generated-name",
|
|
SQL: "select 1",
|
|
ConnectionID: "conn-1",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SaveQuery returned error: %v", err)
|
|
}
|
|
if persisted.Name != "Query 1" {
|
|
t.Fatalf("expected localized generated saved query name, got %q", persisted.Name)
|
|
}
|
|
}
|
|
|
|
func TestSavedQueriesDoesNotHardcodeGeneratedNameChinese(t *testing.T) {
|
|
source, err := os.ReadFile("saved_queries.go")
|
|
if err != nil {
|
|
t.Fatalf("ReadFile returned error: %v", err)
|
|
}
|
|
if strings.Contains(string(source), `fmt.Sprintf("查询-%d", index+1)`) {
|
|
t.Fatal("saved_queries.go still hardcodes generated saved query name")
|
|
}
|
|
}
|
|
|
|
func TestSavedQueryRepositorySerializesConcurrentWrites(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
const count = 24
|
|
var wg sync.WaitGroup
|
|
errCh := make(chan error, count)
|
|
for index := 0; index < count; index++ {
|
|
index := index
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
_, err := app.SaveQuery(connection.SavedQuery{
|
|
ID: fmt.Sprintf("saved-%02d", index),
|
|
Name: fmt.Sprintf("Query %02d", index),
|
|
SQL: fmt.Sprintf("select %d", index),
|
|
ConnectionID: "conn-1",
|
|
DBName: "app",
|
|
CreatedAt: int64(index + 1),
|
|
})
|
|
if err != nil {
|
|
errCh <- err
|
|
}
|
|
}()
|
|
}
|
|
wg.Wait()
|
|
close(errCh)
|
|
|
|
for err := range errCh {
|
|
t.Fatalf("SaveQuery returned error during concurrent write: %v", err)
|
|
}
|
|
|
|
saved, err := app.GetSavedQueries()
|
|
if err != nil {
|
|
t.Fatalf("GetSavedQueries returned error: %v", err)
|
|
}
|
|
if len(saved) != count {
|
|
t.Fatalf("expected %d saved queries after concurrent writes, got %d: %#v", count, len(saved), saved)
|
|
}
|
|
}
|
|
|
|
func TestImportSavedQueriesRebindsLegacyConnectionByUniqueFingerprint(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
current, err := app.SaveConnection(connection.SavedConnectionInput{
|
|
ID: "conn-current",
|
|
Name: "Current",
|
|
Config: connection.ConnectionConfig{
|
|
ID: "conn-current",
|
|
Type: "postgres",
|
|
Host: "db.local",
|
|
Port: 5432,
|
|
User: "app",
|
|
Database: "app",
|
|
Password: "new-secret",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SaveConnection returned error: %v", err)
|
|
}
|
|
|
|
imported, err := app.ImportSavedQueries(connection.SavedQueryImportPayload{
|
|
Queries: []connection.SavedQuery{
|
|
{
|
|
ID: "saved-legacy",
|
|
Name: "Legacy",
|
|
SQL: "select 1",
|
|
ConnectionID: "conn-old",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
},
|
|
},
|
|
LegacyConnections: []connection.SavedConnectionInput{
|
|
{
|
|
ID: "conn-old",
|
|
Name: "Old",
|
|
Config: connection.ConnectionConfig{
|
|
ID: "conn-old",
|
|
Type: "postgres",
|
|
Host: "DB.LOCAL",
|
|
Port: 5432,
|
|
User: "app",
|
|
Database: "app",
|
|
Password: "old-secret",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ImportSavedQueries returned error: %v", err)
|
|
}
|
|
if len(imported) != 1 {
|
|
t.Fatalf("expected 1 imported query, got %#v", imported)
|
|
}
|
|
query := imported[0]
|
|
if query.ConnectionID != current.ID {
|
|
t.Fatalf("expected query to rebind to %s, got %#v", current.ID, query)
|
|
}
|
|
if query.OriginalConnectionID != "conn-old" {
|
|
t.Fatalf("expected original connection id to be preserved, got %#v", query)
|
|
}
|
|
if query.BindingStatus != savedQueryBindingRebound {
|
|
t.Fatalf("expected rebound binding status, got %#v", query)
|
|
}
|
|
if query.ConnectionFingerprint == "" || query.FingerprintVersion != savedQueryFingerprintVersion {
|
|
t.Fatalf("expected fingerprint metadata, got %#v", query)
|
|
}
|
|
}
|
|
|
|
func TestImportSavedQueriesDoesNotRebindAmbiguousFingerprint(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
for _, id := range []string{"conn-a", "conn-b"} {
|
|
if _, err := app.SaveConnection(connection.SavedConnectionInput{
|
|
ID: id,
|
|
Name: id,
|
|
Config: connection.ConnectionConfig{
|
|
ID: id,
|
|
Type: "mysql",
|
|
Host: "db.local",
|
|
Port: 3306,
|
|
User: "app",
|
|
Database: "app",
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("SaveConnection %s returned error: %v", id, err)
|
|
}
|
|
}
|
|
|
|
imported, err := app.ImportSavedQueries(connection.SavedQueryImportPayload{
|
|
Queries: []connection.SavedQuery{
|
|
{
|
|
ID: "saved-ambiguous",
|
|
Name: "Ambiguous",
|
|
SQL: "select 1",
|
|
ConnectionID: "conn-old",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
},
|
|
},
|
|
LegacyConnections: []connection.SavedConnectionInput{
|
|
{
|
|
ID: "conn-old",
|
|
Name: "Old",
|
|
Config: connection.ConnectionConfig{
|
|
ID: "conn-old",
|
|
Type: "mysql",
|
|
Host: "db.local",
|
|
Port: 3306,
|
|
User: "app",
|
|
Database: "app",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("ImportSavedQueries returned error: %v", err)
|
|
}
|
|
if len(imported) != 1 {
|
|
t.Fatalf("expected 1 imported query, got %#v", imported)
|
|
}
|
|
query := imported[0]
|
|
if query.ConnectionID != "conn-old" {
|
|
t.Fatalf("expected ambiguous query to keep old connection id, got %#v", query)
|
|
}
|
|
if query.BindingStatus != savedQueryBindingOrphan {
|
|
t.Fatalf("expected orphan binding status, got %#v", query)
|
|
}
|
|
}
|
|
|
|
func TestGetSavedQueriesRebindsStoredFingerprint(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
current, err := app.SaveConnection(connection.SavedConnectionInput{
|
|
ID: "conn-current",
|
|
Name: "Current",
|
|
Config: connection.ConnectionConfig{
|
|
ID: "conn-current",
|
|
Type: "postgres",
|
|
Host: "db.local",
|
|
Port: 5432,
|
|
User: "app",
|
|
Database: "app",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SaveConnection returned error: %v", err)
|
|
}
|
|
fingerprint, ok := buildSavedConnectionFingerprint(current)
|
|
if !ok {
|
|
t.Fatal("expected current connection fingerprint to be available")
|
|
}
|
|
if err := app.savedQueryRepository().saveAll([]connection.SavedQuery{
|
|
{
|
|
ID: "saved-stored-fingerprint",
|
|
Name: "Stored Fingerprint",
|
|
SQL: "select 1",
|
|
ConnectionID: "conn-old",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
ConnectionFingerprint: fingerprint,
|
|
FingerprintVersion: savedQueryFingerprintVersion,
|
|
},
|
|
}); err != nil {
|
|
t.Fatalf("saveAll returned error: %v", err)
|
|
}
|
|
|
|
queries, err := app.GetSavedQueries()
|
|
if err != nil {
|
|
t.Fatalf("GetSavedQueries returned error: %v", err)
|
|
}
|
|
if len(queries) != 1 {
|
|
t.Fatalf("expected 1 query, got %#v", queries)
|
|
}
|
|
if queries[0].ConnectionID != current.ID {
|
|
t.Fatalf("expected query to rebind to %s, got %#v", current.ID, queries[0])
|
|
}
|
|
if queries[0].OriginalConnectionID != "conn-old" {
|
|
t.Fatalf("expected original connection id to be preserved, got %#v", queries[0])
|
|
}
|
|
if queries[0].BindingStatus != savedQueryBindingRebound {
|
|
t.Fatalf("expected rebound binding status, got %#v", queries[0])
|
|
}
|
|
}
|
|
|
|
func TestRebindSavedQueryUpdatesConnectionAndFingerprint(t *testing.T) {
|
|
app := NewAppWithSecretStore(secretstore.NewUnavailableStore("test"))
|
|
app.configDir = t.TempDir()
|
|
|
|
target, err := app.SaveConnection(connection.SavedConnectionInput{
|
|
ID: "conn-target",
|
|
Name: "Target",
|
|
Config: connection.ConnectionConfig{
|
|
ID: "conn-target",
|
|
Type: "postgres",
|
|
Host: "db.local",
|
|
Port: 5432,
|
|
User: "app",
|
|
Database: "app",
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SaveConnection returned error: %v", err)
|
|
}
|
|
if _, err := app.SaveQuery(connection.SavedQuery{
|
|
ID: "saved-orphan",
|
|
Name: "Orphan",
|
|
SQL: "select 1",
|
|
ConnectionID: "conn-old",
|
|
DBName: "app",
|
|
CreatedAt: 100,
|
|
}); err != nil {
|
|
t.Fatalf("SaveQuery returned error: %v", err)
|
|
}
|
|
|
|
rebound, err := app.RebindSavedQuery("saved-orphan", target.ID)
|
|
if err != nil {
|
|
t.Fatalf("RebindSavedQuery returned error: %v", err)
|
|
}
|
|
if rebound.ConnectionID != target.ID {
|
|
t.Fatalf("expected query to bind to target connection, got %#v", rebound)
|
|
}
|
|
if rebound.OriginalConnectionID != "conn-old" {
|
|
t.Fatalf("expected original connection id to be retained, got %#v", rebound)
|
|
}
|
|
if rebound.BindingStatus != savedQueryBindingActive {
|
|
t.Fatalf("expected active binding status, got %#v", rebound)
|
|
}
|
|
if rebound.ConnectionFingerprint == "" {
|
|
t.Fatalf("expected fingerprint to be stored, got %#v", rebound)
|
|
}
|
|
}
|