Files
MyGoNavi/internal/db/sqlite_i18n_test.go
tianqijiuyun-latiao d13c153f5e feat(i18n): 收口数据库驱动多语言代码
- 提交 internal/db 多驱动用户可见错误与状态文案多语言化

- 补齐数据库驱动多语言测试与六语言 catalog

- 修复 frontend i18n catalog 的 4 个失效 guard
2026-06-22 10:09:45 +08:00

275 lines
7.4 KiB
Go

//go:build gonavi_full_drivers || gonavi_sqlite_driver
package db
import (
"database/sql"
"database/sql/driver"
"io"
"os"
"strings"
"sync"
"testing"
"GoNavi-Wails/internal/connection"
"GoNavi-Wails/shared/i18n"
)
type sqliteI18nEmptyRowsDriver struct{}
type sqliteI18nEmptyRowsConn struct{}
type sqliteI18nEmptyRowsStmt struct{}
type sqliteI18nEmptyRowsRows struct{}
var registerSQLiteI18nEmptyRowsDriverOnce sync.Once
var (
rawSQLiteCreateStatementNotFoundText = string([]rune{0x672a, 0x627e, 0x5230, 0x5efa, 0x8868, 0x8bed, 0x53e5})
rawSQLiteTableNameRequiredText = string([]rune{0x8868, 0x540d, 0x4e0d, 0x80fd, 0x4e3a, 0x7a7a})
rawSQLiteFilePathRequiredText = "SQLite " + string([]rune{0x9700, 0x8981, 0x672c, 0x5730, 0x6570, 0x636e, 0x5e93, 0x6587, 0x4ef6, 0x8def, 0x5f84})
rawSQLiteHostAddressHintText = string([]rune{0x5f53, 0x524d, 0x8f93, 0x5165, 0x770b, 0x8d77, 0x6765, 0x662f, 0x4e3b, 0x673a, 0x5730, 0x5740})
)
func (sqliteI18nEmptyRowsDriver) Open(name string) (driver.Conn, error) {
return sqliteI18nEmptyRowsConn{}, nil
}
func (sqliteI18nEmptyRowsConn) Prepare(query string) (driver.Stmt, error) {
return sqliteI18nEmptyRowsStmt{}, nil
}
func (sqliteI18nEmptyRowsConn) Close() error { return nil }
func (sqliteI18nEmptyRowsConn) Begin() (driver.Tx, error) {
return nil, localizedDatabaseRuntimeError("db.backend.error.transaction_not_open", nil)
}
func (sqliteI18nEmptyRowsStmt) Close() error { return nil }
func (sqliteI18nEmptyRowsStmt) NumInput() int { return -1 }
func (sqliteI18nEmptyRowsStmt) Exec(args []driver.Value) (driver.Result, error) {
return driver.RowsAffected(0), nil
}
func (sqliteI18nEmptyRowsStmt) Query(args []driver.Value) (driver.Rows, error) {
return sqliteI18nEmptyRowsRows{}, nil
}
func (sqliteI18nEmptyRowsRows) Columns() []string {
return []string{"sql"}
}
func (sqliteI18nEmptyRowsRows) Close() error { return nil }
func (sqliteI18nEmptyRowsRows) Next(dest []driver.Value) error {
return io.EOF
}
func openSQLiteI18nEmptyRowsDB(t *testing.T) *sql.DB {
t.Helper()
registerSQLiteI18nEmptyRowsDriverOnce.Do(func() {
sql.Register("sqlite_i18n_empty_rows", sqliteI18nEmptyRowsDriver{})
})
conn, err := sql.Open("sqlite_i18n_empty_rows", "")
if err != nil {
t.Fatalf("open sqlite_i18n_empty_rows test DB failed: %v", err)
}
t.Cleanup(func() {
_ = conn.Close()
})
return conn
}
func TestSQLiteMetadataErrorsUseCurrentLanguage(t *testing.T) {
SetBackendLanguage(i18n.LanguageEnUS)
t.Cleanup(func() {
SetBackendLanguage(i18n.LanguageZhCN)
})
sqlite := &SQLiteDB{}
tests := []struct {
name string
call func() error
want string
unexpected string
}{
{
name: "create statement not found",
call: func() error {
_, err := (&SQLiteDB{conn: openSQLiteI18nEmptyRowsDB(t)}).GetCreateStatement("main", "orders")
return err
},
want: "The CREATE TABLE statement was not found",
unexpected: rawSQLiteCreateStatementNotFoundText,
},
{
name: "columns table name required",
call: func() error {
_, err := sqlite.GetColumns("", " ")
return err
},
want: "Table name is required",
unexpected: rawSQLiteTableNameRequiredText,
},
{
name: "indexes table name required",
call: func() error {
_, err := sqlite.GetIndexes("", " ")
return err
},
want: "Table name is required",
unexpected: rawSQLiteTableNameRequiredText,
},
{
name: "foreign keys table name required",
call: func() error {
_, err := sqlite.GetForeignKeys("", " ")
return err
},
want: "Table name is required",
unexpected: rawSQLiteTableNameRequiredText,
},
{
name: "triggers table name required",
call: func() error {
_, err := sqlite.GetTriggers("", " ")
return err
},
want: "Table name is required",
unexpected: rawSQLiteTableNameRequiredText,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := tc.call()
if err == nil {
t.Fatal("expected SQLite metadata call to fail")
}
if err.Error() != tc.want {
t.Fatalf("expected %q, got %q", tc.want, err.Error())
}
if strings.Contains(err.Error(), tc.unexpected) {
t.Fatalf("expected no raw Chinese SQLite metadata text, got %q", err.Error())
}
})
}
}
func TestSQLiteDSNValidationErrorsUseCurrentLanguage(t *testing.T) {
SetBackendLanguage(i18n.LanguageEnUS)
t.Cleanup(func() {
SetBackendLanguage(i18n.LanguageZhCN)
})
tests := []struct {
name string
config connection.ConnectionConfig
want string
unexpected string
}{
{
name: "empty path",
config: connection.ConnectionConfig{Type: "sqlite"},
want: "SQLite requires a local database file path (for example /path/to/demo.sqlite)",
unexpected: rawSQLiteFilePathRequiredText,
},
{
name: "host port",
config: connection.ConnectionConfig{Type: "sqlite", Host: "localhost:3306"},
want: "SQLite requires a local database file path; the current input looks like a host address: localhost:3306",
unexpected: rawSQLiteHostAddressHintText,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
err := (&SQLiteDB{}).Connect(tc.config)
if err == nil {
t.Fatal("expected SQLite DSN validation error")
}
if err.Error() != tc.want {
t.Fatalf("expected %q, got %q", tc.want, err.Error())
}
if strings.Contains(err.Error(), tc.unexpected) {
t.Fatalf("expected no raw Chinese SQLite DSN validation text, got %q", err.Error())
}
})
}
}
func TestSQLiteMetadataErrorSourcesUseI18nKeys(t *testing.T) {
sourceBytes, err := os.ReadFile("sqlite_impl.go")
if err != nil {
t.Fatalf("read sqlite_impl.go: %v", err)
}
source := string(sourceBytes)
for _, rawMessage := range []string{
`fmt.Errorf("` + rawSQLiteCreateStatementNotFoundText + `")`,
`fmt.Errorf("` + rawSQLiteTableNameRequiredText + `")`,
} {
if strings.Contains(source, rawMessage) {
t.Fatalf("sqlite_impl.go still contains raw SQLite metadata text %q", rawMessage)
}
}
for _, key := range []string{
"db.backend.error.create_table_statement_not_found",
"db.backend.error.table_name_required",
} {
if !strings.Contains(source, key) {
t.Fatalf("sqlite_impl.go does not reference i18n key %q", key)
}
}
}
func TestSQLiteDSNValidationErrorSourcesUseI18nKeys(t *testing.T) {
sourceBytes, err := os.ReadFile("sqlite_impl.go")
if err != nil {
t.Fatalf("read sqlite_impl.go: %v", err)
}
source := string(sourceBytes)
for _, rawMessage := range []string{
rawSQLiteFilePathRequiredText,
rawSQLiteHostAddressHintText,
} {
if strings.Contains(source, rawMessage) {
t.Fatalf("sqlite_impl.go still contains raw SQLite DSN validation text %q", rawMessage)
}
}
for _, key := range []string{
"db.backend.error.sqlite_file_path_required",
"db.backend.error.sqlite_host_port_not_file_path",
} {
if !strings.Contains(source, key) {
t.Fatalf("sqlite_impl.go does not reference i18n key %q", key)
}
}
}
func TestSQLiteDSNValidationErrorCatalogKeysExist(t *testing.T) {
catalogs, err := i18n.LoadCatalogs()
if err != nil {
t.Fatalf("LoadCatalogs() error = %v", err)
}
keys := []string{
"db.backend.error.sqlite_file_path_required",
"db.backend.error.sqlite_host_port_not_file_path",
}
for _, language := range i18n.SupportedLanguages() {
catalog := catalogs[language]
for _, key := range keys {
if strings.TrimSpace(catalog[key]) == "" {
t.Fatalf("%s catalog missing SQLite DSN validation key %q", language, key)
}
}
}
}