package store import ( "io/fs" "slices" "testing" ) // Diese Tests laufen immer, auch ohne PostgreSQL. Die Tests gegen eine echte // PostgreSQL-Datenbank stehen in postgres_test.go. func TestPlatzhalterNummerieren(t *testing.T) { faelle := []struct { name, ein, aus string }{ {"ohne Platzhalter", `SELECT 1`, `SELECT 1`}, {"der Reihe nach", `SELECT a FROM t WHERE x = ? AND y = ?`, `SELECT a FROM t WHERE x = $1 AND y = $2`}, {"IN-Liste", `WHERE id IN (?, ?, ?) LIMIT ?`, `WHERE id IN ($1, $2, $3) LIMIT $4`}, {"Text bleibt", `WHERE a = 'Wie geht das?' AND b = ?`, `WHERE a = 'Wie geht das?' AND b = $1`}, {"verdoppeltes Hochkomma", `WHERE a = 'It''s ?' AND b = ?`, `WHERE a = 'It''s ?' AND b = $1`}, {"Bezeichner bleibt", `SELECT "was?" FROM t WHERE a = ?`, `SELECT "was?" FROM t WHERE a = $1`}, {"Zeilenkommentar", "SELECT a -- warum?\nFROM t WHERE b = ?", "SELECT a -- warum?\nFROM t WHERE b = $1"}, {"Blockkommentar", `SELECT /* wer? */ a FROM t WHERE b = ?`, `SELECT /* wer? */ a FROM t WHERE b = $1`}, {"offener Text am Ende", `WHERE a = ? AND b = 'kaputt?`, `WHERE a = $1 AND b = 'kaputt?`}, {"Kommentar am Ende ohne Zeilenumbruch", `WHERE a = ? -- fertig?`, `WHERE a = $1 -- fertig?`}, {"zweistellig", `(?,?,?,?,?,?,?,?,?,?,?)`, `($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)`}, } for _, f := range faelle { t.Run(f.name, func(t *testing.T) { if got := platzhalterNummerieren(f.ein); got != f.aus { t.Errorf("\nein: %s\nist: %s\nsoll: %s", f.ein, got, f.aus) } }) } } func TestSQLiteRebindLaesstAbfrageUnveraendert(t *testing.T) { abfrage := `SELECT a FROM t WHERE x = ?` if got := (sqliteDialekt{}).rebind(abfrage); got != abfrage { t.Errorf("SQLite hat die Abfrage verändert: %s", got) } } // TestMigrationsordnerGleich prüft, dass sqlite/ und postgres/ dieselben // Migrationen enthalten. Eine Migration, die nur in einem Ordner liegt, würde // die beiden Datenbanken unbemerkt auseinanderlaufen lassen — siehe // AUSBAUSTUFEN.md 4.10. // // Ob die Migrationen auch dasselbe SCHEMA ergeben, prüft TestSchemaGleich // (braucht PostgreSQL). func TestMigrationsordnerGleich(t *testing.T) { for _, quelle := range []struct { fsys fs.FS dir string }{ {migrationsFS, "migrations"}, {controlMigrationsFS, "migrations_control"}, } { dateien := func(unterordner string) []string { eintraege, err := fs.ReadDir(quelle.fsys, quelle.dir+"/"+unterordner) if err != nil { t.Fatalf("%s/%s: %v", quelle.dir, unterordner, err) } var namen []string for _, e := range eintraege { namen = append(namen, e.Name()) } return namen } sqlite := dateien((sqliteDialekt{}).migrationsUnterordner()) postgres := dateien((postgresDialekt{}).migrationsUnterordner()) if len(sqlite) == 0 { t.Errorf("%s: keine Migrationen gefunden", quelle.dir) } if !slices.Equal(sqlite, postgres) { t.Errorf("%s: Ordner unterscheiden sich\nsqlite: %v\npostgres: %v", quelle.dir, sqlite, postgres) } } } func TestWorkspaceSchema(t *testing.T) { got, err := WorkspaceSchema("mue9bh3x-1c0e1120") if err != nil { t.Fatal(err) } if got != "munin_ws_mue9bh3x_1c0e1120" { t.Errorf("Schema = %q", got) } // Eine echte ID muss immer passen. if _, err := WorkspaceSchema(NewID()); err != nil { t.Errorf("NewID() ergibt keinen gültigen Schemanamen: %v", err) } // Alles, was nicht wie eine ID aussieht, darf nie in einen Schemanamen. for _, kaputt := range []string{"", "Groß", "a b", "a;DROP", `a"b`, "../x", "ä"} { if _, err := WorkspaceSchema(kaputt); err == nil { t.Errorf("WorkspaceSchema(%q) hätte abgelehnt werden müssen", kaputt) } } // PostgreSQL würde längere Namen stillschweigend kürzen. lang := "" for len(lang) < 60 { lang += "abcdefghij" } if _, err := WorkspaceSchema(lang); err == nil { t.Error("zu lange ID wurde nicht abgelehnt") } } func TestTextLiteral(t *testing.T) { faelle := map[string]string{ "Vertrieb": `E'Vertrieb'`, "O'Brien": `E'O''Brien'`, `C:\temp`: `E'C:\\temp'`, `\'; DROP x; --`: `E'\\''; DROP x; --'`, "mit\x00Null": `E'mitNull'`, "Ärger & Öl": `E'Ärger & Öl'`, } for ein, soll := range faelle { if got := textLiteral(ein); got != soll { t.Errorf("textLiteral(%q) = %s, erwartet %s", ein, got, soll) } } }