internal/app/langpack/seed_test.go
Rama main · réplica de lectura del repositorio original.
package langpack
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"telesrv/internal/domain"
"telesrv/internal/store/memory"
)
func TestSeedDirectoryRejectsChangedSources(t *testing.T) {
for _, tc := range []struct {
name string
content string
missingSize bool
missingModTime bool
symlink bool
}{
{name: "same_file_size", content: `"key" = "new";`},
{name: "legacy_catalog_same_size", content: `"key" = "new";`, missingSize: true, missingModTime: true},
{name: "legacy_catalog_changed_size", content: `"key" = "a much longer translation";`, missingSize: true, missingModTime: true},
{name: "size_only_catalog", content: `"key" = "new";`, missingModTime: true},
{name: "symlink_target", content: `"key" = "new";`, symlink: true},
} {
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
root := t.TempDir()
path := filepath.Join(root, "tdesktop_en_v1.strings")
sourcePath := path
if tc.symlink {
sourcePath = filepath.Join(t.TempDir(), "source.strings")
if err := os.Symlink(sourcePath, path); err != nil {
t.Skipf("symlink unavailable: %v", err)
}
}
writeLangPackFixture(t, sourcePath, `"key" = "old";`)
mtime := time.Unix(1700000000, 0)
if err := os.Chtimes(sourcePath, mtime, mtime); err != nil {
t.Fatal(err)
}
store := memory.NewLangPackStore()
if _, err := NewService(store).SeedDirectory(ctx, root); err != nil {
t.Fatal(err)
}
previous, err := store.GetSeedCatalog(ctx, seedCatalogID(root))
if err != nil || len(previous.Packs) != 1 {
t.Fatalf("initial catalog=%+v err=%v", previous, err)
}
if tc.missingSize {
previous.Packs[0].Size = 0
}
if tc.missingModTime {
previous.Packs[0].ModTime = 0
}
if err := store.PutSeedCatalog(ctx, previous); err != nil {
t.Fatal(err)
}
writeLangPackFixture(t, sourcePath, tc.content)
mtime = mtime.Add(time.Minute)
if err := os.Chtimes(sourcePath, mtime, mtime); err != nil {
t.Fatal(err)
}
if seeded, err := NewService(store).SeedDirectory(ctx, root); err == nil || !strings.Contains(err.Error(), "without version bump") {
t.Fatalf("changed source accepted without version bump: seeded=%d err=%v", seeded, err)
}
pack, err := store.GetPack(ctx, "tdesktop", "en", 0)
if err != nil || stringValue(pack.Strings, "key") != "old" {
t.Fatalf("rejected mutation changed pack=%+v err=%v", pack, err)
}
current, err := store.GetSeedCatalog(ctx, seedCatalogID(root))
if err != nil || !domain.SameSeedCatalogSnapshot(previous, current) {
t.Fatalf("rejected mutation changed catalog=%+v err=%v", current, err)
}
})
}
}
type countingSeedCatalogStore struct {
*memory.LangPackStore
reconcileCalls int
catalogWrites int
}
func (s *countingSeedCatalogStore) ReconcileSeed(ctx context.Context, seed domain.LangPackSeed) (int, error) {
s.reconcileCalls++
return s.LangPackStore.ReconcileSeed(ctx, seed)
}
func (s *countingSeedCatalogStore) PutSeedCatalog(ctx context.Context, catalog domain.LangPackSeedCatalog) error {
s.catalogWrites++
return s.LangPackStore.PutSeedCatalog(ctx, catalog)
}
func TestSeedDirectoryRefreshesStatMetadataWithoutReimport(t *testing.T) {
ctx := context.Background()
root := t.TempDir()
path := filepath.Join(root, "tdesktop_en_v1.strings")
writeLangPackFixture(t, path, `"key" = "old";`)
store := &countingSeedCatalogStore{LangPackStore: memory.NewLangPackStore()}
if seeded, err := NewService(store).SeedDirectory(ctx, root); err != nil || seeded != 1 {
t.Fatalf("initial seed=%d err=%v", seeded, err)
}
previous, err := store.GetSeedCatalog(ctx, seedCatalogID(root))
if err != nil || len(previous.Packs) != 1 {
t.Fatalf("initial catalog=%+v err=%v", previous, err)
}
legacy := previous
legacy.Packs = append([]domain.LangPackSeedCatalogEntry(nil), previous.Packs...)
legacy.Packs[0].Size = 0
legacy.Packs[0].ModTime = 0
if err := store.LangPackStore.PutSeedCatalog(ctx, legacy); err != nil {
t.Fatal(err)
}
for _, step := range []struct {
name string
writes int
}{
{name: "upgrade_legacy_catalog", writes: 1},
{name: "touch_source", writes: 2},
{name: "unchanged_restart", writes: 2},
} {
t.Run(step.name, func(t *testing.T) {
if step.name == "touch_source" {
mtime := time.Unix(0, previous.Packs[0].ModTime).Add(time.Minute)
if err := os.Chtimes(path, mtime, mtime); err != nil {
t.Fatal(err)
}
}
if seeded, err := NewService(store).SeedDirectory(ctx, root); err != nil || seeded != 0 {
t.Fatalf("unchanged seed=%d err=%v", seeded, err)
}
if store.reconcileCalls != 1 || store.catalogWrites != step.writes {
t.Fatalf("reconciles=%d catalog writes=%d, want 1/%d", store.reconcileCalls, store.catalogWrites, step.writes)
}
info, err := os.Stat(path)
if err != nil {
t.Fatal(err)
}
catalog, err := store.GetSeedCatalog(ctx, seedCatalogID(root))
if err != nil || len(catalog.Packs) != 1 {
t.Fatalf("refreshed catalog=%+v err=%v", catalog, err)
}
entry := catalog.Packs[0]
if entry.Size != info.Size() || entry.ModTime != info.ModTime().UnixNano() || entry.SourceHash != previous.Packs[0].SourceHash {
t.Fatalf("refreshed entry=%+v, want current stat and unchanged source hash", entry)
}
})
}
}