feat(store): add account from_address field + v2 migration

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-23 20:16:15 +01:00
parent a4c49d4aca
commit cdffb15004
5 changed files with 143 additions and 23 deletions
+63 -2
View File
@@ -1,6 +1,7 @@
package store
import (
"database/sql"
"path/filepath"
"testing"
)
@@ -28,7 +29,7 @@ func TestOpenCreatesSchemaAndIsIdempotent(t *testing.T) {
t.Fatalf("first Open: %v", err)
}
v, err := s.GetSetting("schema_version")
if err != nil || v != "1" {
if err != nil || v != "2" {
t.Fatalf("schema_version: %q err=%v", v, err)
}
s.Close()
@@ -39,7 +40,7 @@ func TestOpenCreatesSchemaAndIsIdempotent(t *testing.T) {
t.Fatalf("second Open: %v", err)
}
defer s2.Close()
if v, _ := s2.GetSetting("schema_version"); v != "1" {
if v, _ := s2.GetSetting("schema_version"); v != "2" {
t.Fatalf("schema_version after reopen: %q", v)
}
}
@@ -104,3 +105,63 @@ func TestForeignKeyCascade(t *testing.T) {
t.Fatalf("whitelist_in row not cascade-deleted: count=%d err=%v", count, err)
}
}
func TestOpenMigratesV1AddsFromAddress(t *testing.T) {
p := filepath.Join(t.TempDir(), "emcli.db")
// Hand-build a v1 database: accounts table WITHOUT from_address, a settings
// table pinned at schema_version=1, and one pre-existing account row.
raw, err := sql.Open("sqlite", p)
if err != nil {
t.Fatalf("sql.Open: %v", err)
}
const v1Schema = `
CREATE TABLE settings (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
mode TEXT NOT NULL,
imap_host TEXT NOT NULL,
imap_port INTEGER NOT NULL,
imap_security TEXT NOT NULL,
smtp_host TEXT, smtp_port INTEGER, smtp_security TEXT,
auth_type TEXT NOT NULL,
username TEXT NOT NULL,
enc_password BLOB,
enc_oauth_client_id BLOB, enc_oauth_client_secret BLOB, enc_oauth_refresh_token BLOB,
whitelist_in_enabled INTEGER NOT NULL DEFAULT 0,
whitelist_out_enabled INTEGER NOT NULL DEFAULT 0,
subject_regex TEXT,
process_backlog INTEGER NOT NULL DEFAULT 0
);
INSERT INTO settings(key,value) VALUES ('schema_version','1');
INSERT INTO accounts(name,mode,imap_host,imap_port,imap_security,auth_type,username)
VALUES ('legacy','RO','imap.example.com',993,'tls','password','login@example.com');
`
if _, err := raw.Exec(v1Schema); err != nil {
t.Fatalf("seed v1 schema: %v", err)
}
raw.Close()
// Open via the store: the migration must add from_address and bump to v2.
s, err := Open(p)
if err != nil {
t.Fatalf("Open (migrate): %v", err)
}
defer s.Close()
if v, _ := s.GetSetting("schema_version"); v != "2" {
t.Fatalf("schema_version after migrate: %q, want 2", v)
}
// ListAccounts SELECTs from_address; it would error if the column were missing.
accs, err := s.ListAccounts()
if err != nil {
t.Fatalf("ListAccounts after migrate: %v", err)
}
if len(accs) != 1 || accs[0].FromAddress != "" {
t.Fatalf("legacy account wrong after migrate: %+v", accs)
}
if got := accs[0].SendFrom(); got != "login@example.com" {
t.Fatalf("legacy account should send from username, got %q", got)
}
}