Ctrl K
ComunidadGuías…

internal/store/postgres/langpack_seed_integration_test.go

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

Archivo original ↗Preguntar a KiBot
package postgres

import (
	"context"
	"path/filepath"
	"strings"
	"testing"
	"time"

	applangpack "telesrv/internal/app/langpack"
	"telesrv/internal/domain"
)

func TestLangPackSeedReconciliationPostgres(t *testing.T) {
	pool := testPool(t)
	ctx := context.Background()
	packName := "seedtest-" + randomSuffix(t)
	store := NewLangPackStore(pool)
	t.Cleanup(func() {
		_, _ = pool.Exec(ctx, "DELETE FROM lang_pack_strings WHERE lang_pack = $1", packName)
		_, _ = pool.Exec(ctx, "DELETE FROM lang_packs WHERE lang_pack = $1", packName)
		_, _ = pool.Exec(ctx, "DELETE FROM seed_states WHERE key LIKE $1 OR key = $2 OR key = $3", "langpack:v1:entry:"+packName+":%", "langpack:v1:catalog:"+packName, "langpack:v2:catalog:"+packName)
	})

	seedV1 := domain.LangPackSeed{
		Catalog: packName,
		Scopes:  []string{packName},
		Packs: []domain.LangPackSeedEntry{{
			SourceHash:    "source-v1",
			ContentHash:   "hash-v1",
			StringsCount:  2,
			Size:          1234,
			ModTime:       time.Unix(1700000000, 123).UnixNano(),
			ContentLoaded: true,
			Pack: domain.LangPack{
				LangPack: packName,
				LangCode: "fr",
				Version:  1,
				Strings: []domain.LangPackString{
					{Key: "LanguageName", Value: "Français"},
					{Key: "old", Value: "old"},
				},
			},
		}},
	}
	written, err := store.ReconcileSeed(ctx, seedV1)
	if err != nil || written != 2 {
		t.Fatalf("reconcile v1 = %d, %v", written, err)
	}
	seedV1Unloaded := seedV1
	seedV1Unloaded.Packs = append([]domain.LangPackSeedEntry(nil), seedV1.Packs...)
	seedV1Unloaded.Packs[0].Pack.Strings = nil
	seedV1Unloaded.Packs[0].ContentLoaded = false
	if written, err := store.ReconcileSeed(ctx, seedV1Unloaded); err != nil || written != 0 {
		t.Fatalf("reconcile unchanged v1 = %d, %v", written, err)
	}
	catalog, err := store.GetSeedCatalog(ctx, packName)
	if err != nil || !domain.SameSeedCatalogSnapshot(catalog, seedV1.CatalogSnapshot()) {
		t.Fatalf("seed catalog = %+v, %v", catalog, err)
	}
	// A stat-only refresh must keep the source hash and preserve nanosecond precision through the JSON round-trip.
	catalog.Packs[0].ModTime++
	if err := store.PutSeedCatalog(ctx, catalog); err != nil {
		t.Fatalf("refresh seed catalog: %v", err)
	}
	refreshed, err := store.GetSeedCatalog(ctx, packName)
	if err != nil || !domain.SameSeedCatalogSnapshot(refreshed, catalog) {
		t.Fatalf("refreshed seed catalog = %+v, %v", refreshed, err)
	}

	seedV2 := domain.LangPackSeed{
		Catalog: packName,
		Scopes:  []string{packName},
		Packs: []domain.LangPackSeedEntry{{
			SourceHash:    "source-v2",
			ContentHash:   "hash-v2",
			StringsCount:  2,
			ContentLoaded: true,
			Pack: domain.LangPack{
				LangPack: packName,
				LangCode: "fr",
				Version:  2,
				Strings: []domain.LangPackString{
					{Key: "LanguageName", Value: "Français v2"},
					{Key: "new", Value: "new"},
				},
			},
		}},
	}
	written, err = store.ReconcileSeed(ctx, seedV2)
	if err != nil || written != 2 {
		t.Fatalf("reconcile v2 = %d, %v", written, err)
	}
	pack, err := store.GetPack(ctx, packName, "fr", 0)
	if err != nil || pack.Version != 2 || len(pack.Strings) != 2 || postgresLangPackValue(pack.Strings, "old") != "" || postgresLangPackValue(pack.Strings, "new") != "new" {
		t.Fatalf("replaced postgres pack = %+v, err %v", pack, err)
	}

	mutated := seedV2
	mutated.Packs = append([]domain.LangPackSeedEntry(nil), seedV2.Packs...)
	mutated.Packs[0].ContentHash = "changed-without-version"
	if _, err := store.ReconcileSeed(ctx, mutated); err == nil || !strings.Contains(err.Error(), "without version bump") {
		t.Fatalf("same-version mutation error = %v", err)
	}
	rollback := seedV2
	rollback.Packs = append([]domain.LangPackSeedEntry(nil), seedV2.Packs...)
	rollback.Packs[0].Pack.Version = 1
	rollback.Packs[0].ContentHash = "rollback"
	if n, err := store.ReconcileSeed(ctx, rollback); err != nil || n != 0 {
		t.Fatalf("older seed reconcile = %d, %v", n, err)
	}
	keptPack, keptErr := store.GetPack(ctx, packName, "fr", 0)
	if keptErr != nil || keptPack.Version != 2 || len(keptPack.Strings) != 2 {
		t.Fatalf("pack regressed after older seed = %+v, err %v", keptPack, keptErr)
	}

	if written, err := store.ReconcileSeed(ctx, domain.LangPackSeed{Catalog: packName}); err != nil || written != 0 {
		t.Fatalf("remove missing language = %d, %v", written, err)
	}
	languages, err := store.ListLanguages(ctx, packName)
	if err != nil || len(languages) != 0 {
		t.Fatalf("languages after manifest removal = %+v, err %v", languages, err)
	}
}

func postgresLangPackValue(items []domain.LangPackString, key string) string {
	for _, item := range items {
		if item.Key == key {
			return item.Value
		}
	}
	return ""
}

func TestBundledLangPackSeedPostgres(t *testing.T) {
	pool := testPool(t)
	ctx := context.Background()
	scopes := []string{"android", "android_x", "ios", "macos", "tdesktop", "weba"}
	t.Cleanup(func() {
		_, _ = pool.Exec(ctx, "DELETE FROM lang_pack_strings WHERE lang_pack = ANY($1::text[])", scopes)
		_, _ = pool.Exec(ctx, "DELETE FROM lang_packs WHERE lang_pack = ANY($1::text[])", scopes)
		for _, scope := range scopes {
			_, _ = pool.Exec(ctx, "DELETE FROM seed_states WHERE key LIKE $1", "langpack:v1:entry:"+scope+":%")
		}
		_, _ = pool.Exec(ctx, "DELETE FROM seed_states WHERE key = 'langpack:v1:catalog:langpack'")
		_, _ = pool.Exec(ctx, "DELETE FROM seed_states WHERE key = 'langpack:v2:catalog:langpack'")
	})

	service := applangpack.NewService(NewLangPackStore(pool))
	root := filepath.Join("..", "..", "..", "data", "langpack")
	started := time.Now()
	seeded, err := service.SeedDirectory(ctx, root)
	firstDuration := time.Since(started)
	if err != nil {
		t.Fatalf("seed bundled langpacks to postgres: %v", err)
	}
	if seeded < 50000 {
		t.Fatalf("seeded bundled strings = %d, want full catalog", seeded)
	}

	started = time.Now()
	seededAgain, err := service.SeedDirectory(ctx, root)
	secondDuration := time.Since(started)
	if err != nil || seededAgain != 0 {
		t.Fatalf("reconcile unchanged bundled langpacks = %d, %v", seededAgain, err)
	}
	var metadataCount, activeCount int64
	if err := pool.QueryRow(ctx, `
SELECT
  (SELECT COALESCE(sum(strings_count), 0) FROM lang_packs WHERE lang_pack = ANY($1::text[])),
  (SELECT count(*) FROM lang_pack_strings WHERE lang_pack = ANY($1::text[]) AND NOT deleted)
`, scopes).Scan(&metadataCount, &activeCount); err != nil {
		t.Fatalf("count seeded langpack rows: %v", err)
	}
	if metadataCount != activeCount {
		t.Fatalf("seeded strings_count = %d, active rows = %d", metadataCount, activeCount)
	}
	t.Logf("bundled langpack seed: first=%s unchanged=%s strings=%d", firstDuration, secondDuration, metadataCount)
}