Ctrl K
ComunidadGuías…

internal/app/langpack/seed_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
			}
		})
	}
}