package main import ( "os" "path/filepath" "strings" "testing" "time" ) func TestModelStoreSQLiteCreatesSchemaAndPersists(t *testing.T) { dbPath := filepath.Join(t.TempDir(), "models.sqlite") store := NewModelStore(ModelStoreConfig{ Type: databaseTypeSQLite, DSN: dbPath, SQLitePath: dbPath, }) defer store.Close() if err := store.Load(); err != nil { t.Fatalf("load sqlite store: %v", err) } if _, err := os.Stat(dbPath); err != nil { t.Fatalf("sqlite file not created: %v", err) } model, err := store.EnsureByHostModelKey("chaturbate.com", "alice") if err != nil { t.Fatalf("ensure model: %v", err) } if model.ID != "chaturbate.com:alice" { t.Fatalf("unexpected model id: %q", model.ID) } if err := store.SetLastSeenOnline("chaturbate.com", "alice", true, time.Now().UTC().Format(time.RFC3339Nano)); err != nil { t.Fatalf("set last seen online: %v", err) } models := store.List() if len(models) != 1 { t.Fatalf("expected 1 model, got %d", len(models)) } if models[0].LastSeenOnline == nil || !*models[0].LastSeenOnline { t.Fatalf("lastSeenOnline not persisted: %#v", models[0].LastSeenOnline) } meta := store.Meta() if meta.Count != 1 { t.Fatalf("expected meta count 1, got %d", meta.Count) } if meta.UpdatedAt == "" { t.Fatal("expected meta updatedAt") } } func TestMigrateModelStoreDataSQLiteToSQLiteReplacesTarget(t *testing.T) { tmp := t.TempDir() sourcePath := filepath.Join(tmp, "source.sqlite") targetPath := filepath.Join(tmp, "target.sqlite") sourceConfig := ModelStoreConfig{Type: databaseTypeSQLite, DSN: sourcePath, SQLitePath: sourcePath} targetConfig := ModelStoreConfig{Type: databaseTypeSQLite, DSN: targetPath, SQLitePath: targetPath} source := NewModelStore(sourceConfig) defer source.Close() if err := source.Load(); err != nil { t.Fatalf("load source: %v", err) } if _, err := source.EnsureByHostModelKey("chaturbate.com", "alice"); err != nil { t.Fatalf("ensure source model: %v", err) } if err := source.SetProfileImage("chaturbate.com", "alice", "https://example.test/a.jpg", "image/jpeg", []byte{1, 2, 3, 4}, time.Now().UTC().Format(time.RFC3339Nano)); err != nil { t.Fatalf("set profile image: %v", err) } if err := source.SetBioContext("chaturbate.com", "alice", `{"ok":true}`, time.Now().UTC().Format(time.RFC3339Nano)); err != nil { t.Fatalf("set bio context: %v", err) } target := NewModelStore(targetConfig) if err := target.Load(); err != nil { t.Fatalf("load target: %v", err) } if _, err := target.EnsureByHostModelKey("chaturbate.com", "stale"); err != nil { t.Fatalf("ensure stale target model: %v", err) } _ = target.Close() _ = source.Close() count, err := migrateModelStoreData(sourceConfig, targetConfig) if err != nil { t.Fatalf("migrate sqlite to sqlite: %v", err) } if count != 1 { t.Fatalf("expected 1 migrated row, got %d", count) } migrated := NewModelStore(targetConfig) defer migrated.Close() if err := migrated.Load(); err != nil { t.Fatalf("load migrated target: %v", err) } models := migrated.List() if len(models) != 1 { t.Fatalf("expected target replacement with 1 row, got %d", len(models)) } if models[0].ModelKey != "alice" { t.Fatalf("unexpected migrated model: %q", models[0].ModelKey) } if models[0].ProfileImageCached == "" { t.Fatal("expected migrated profile image blob marker") } bio, _, ok, err := migrated.GetBioContext("chaturbate.com", "alice") if err != nil { t.Fatalf("get migrated bio context: %v", err) } if !ok || bio != `{"ok":true}` { t.Fatalf("unexpected migrated bio context ok=%v bio=%q", ok, bio) } } func TestMoveSQLiteDatabaseFilesMovesSidecars(t *testing.T) { tmp := t.TempDir() sourcePath := filepath.Join(tmp, "old", "models.sqlite") targetPath := filepath.Join(tmp, "new", "models.sqlite") if err := os.MkdirAll(filepath.Dir(sourcePath), 0755); err != nil { t.Fatalf("mkdir source: %v", err) } files := map[string]string{ sourcePath: "main", sourcePath + "-wal": "wal", sourcePath + "-shm": "shm", sourcePath + "-journal": "journal", } for path, content := range files { if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatalf("write %s: %v", path, err) } } if err := moveSQLiteDatabaseFiles(sourcePath, targetPath); err != nil { t.Fatalf("move sqlite files: %v", err) } for source, content := range files { if _, err := os.Stat(source); !os.IsNotExist(err) { t.Fatalf("expected source removed %s, err=%v", source, err) } target := strings.Replace(source, sourcePath, targetPath, 1) b, err := os.ReadFile(target) if err != nil { t.Fatalf("read target %s: %v", target, err) } if string(b) != content { t.Fatalf("unexpected target content for %s: %q", target, string(b)) } } }