316 lines
9.5 KiB
Go
316 lines
9.5 KiB
Go
package localdb
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestRunLocalMigrationsBackfillsExistingConfigurations(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "legacy_local.db")
|
|
|
|
local, err := New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("open localdb: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = local.Close() })
|
|
|
|
cfg := &LocalConfiguration{
|
|
UUID: "legacy-cfg",
|
|
Name: "Legacy",
|
|
Items: LocalConfigItems{},
|
|
SyncStatus: "pending",
|
|
OriginalUsername: "tester",
|
|
IsActive: true,
|
|
}
|
|
if err := local.SaveConfiguration(cfg); err != nil {
|
|
t.Fatalf("save seed config: %v", err)
|
|
}
|
|
if err := local.DB().Where("configuration_uuid = ?", "legacy-cfg").Delete(&LocalConfigurationVersion{}).Error; err != nil {
|
|
t.Fatalf("delete seed versions: %v", err)
|
|
}
|
|
if err := local.DB().Model(&LocalConfiguration{}).
|
|
Where("uuid = ?", "legacy-cfg").
|
|
Update("current_version_id", nil).Error; err != nil {
|
|
t.Fatalf("clear current_version_id: %v", err)
|
|
}
|
|
if err := local.DB().Where("1=1").Delete(&LocalSchemaMigration{}).Error; err != nil {
|
|
t.Fatalf("clear migration records: %v", err)
|
|
}
|
|
|
|
if err := runLocalMigrations(local.DB()); err != nil {
|
|
t.Fatalf("run local migrations manually: %v", err)
|
|
}
|
|
|
|
migratedCfg, err := local.GetConfigurationByUUID("legacy-cfg")
|
|
if err != nil {
|
|
t.Fatalf("get migrated config: %v", err)
|
|
}
|
|
if migratedCfg.CurrentVersionID == nil || *migratedCfg.CurrentVersionID == "" {
|
|
t.Fatalf("expected current_version_id after migration")
|
|
}
|
|
if !migratedCfg.IsActive {
|
|
t.Fatalf("expected migrated config to be active")
|
|
}
|
|
|
|
var versionCount int64
|
|
if err := local.DB().Model(&LocalConfigurationVersion{}).
|
|
Where("configuration_uuid = ?", "legacy-cfg").
|
|
Count(&versionCount).Error; err != nil {
|
|
t.Fatalf("count versions: %v", err)
|
|
}
|
|
if versionCount != 1 {
|
|
t.Fatalf("expected 1 backfilled version, got %d", versionCount)
|
|
}
|
|
|
|
var migrationCount int64
|
|
if err := local.DB().Model(&LocalSchemaMigration{}).Count(&migrationCount).Error; err != nil {
|
|
t.Fatalf("count local migrations: %v", err)
|
|
}
|
|
if migrationCount == 0 {
|
|
t.Fatalf("expected local migrations to be recorded")
|
|
}
|
|
}
|
|
|
|
func TestRunLocalMigrationsFixesPricelistVersionUniqueIndex(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "pricelist_index_fix.db")
|
|
|
|
local, err := New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("open localdb: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = local.Close() })
|
|
|
|
if err := local.SaveLocalPricelist(&LocalPricelist{
|
|
ServerID: 10,
|
|
Version: "2026-02-06-001",
|
|
Name: "v1",
|
|
CreatedAt: time.Now().Add(-time.Hour),
|
|
SyncedAt: time.Now().Add(-time.Hour),
|
|
}); err != nil {
|
|
t.Fatalf("save first pricelist: %v", err)
|
|
}
|
|
|
|
if err := local.DB().Exec(`
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_local_pricelists_version_legacy
|
|
ON local_pricelists(version)
|
|
`).Error; err != nil {
|
|
t.Fatalf("create legacy unique version index: %v", err)
|
|
}
|
|
|
|
if err := local.DB().Where("id = ?", "2026_02_06_pricelist_index_fix").
|
|
Delete(&LocalSchemaMigration{}).Error; err != nil {
|
|
t.Fatalf("delete migration record: %v", err)
|
|
}
|
|
|
|
if err := runLocalMigrations(local.DB()); err != nil {
|
|
t.Fatalf("rerun local migrations: %v", err)
|
|
}
|
|
|
|
if err := local.SaveLocalPricelist(&LocalPricelist{
|
|
ServerID: 11,
|
|
Version: "2026-02-06-001",
|
|
Name: "v1-duplicate-version",
|
|
CreatedAt: time.Now(),
|
|
SyncedAt: time.Now(),
|
|
}); err != nil {
|
|
t.Fatalf("save second pricelist with duplicate version: %v", err)
|
|
}
|
|
|
|
var count int64
|
|
if err := local.DB().Model(&LocalPricelist{}).Count(&count).Error; err != nil {
|
|
t.Fatalf("count pricelists: %v", err)
|
|
}
|
|
if count != 2 {
|
|
t.Fatalf("expected 2 pricelists, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestRunLocalMigrationsDeduplicatesConfigurationVersionsBySpecAndPrice(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "versions_dedup.db")
|
|
|
|
local, err := New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("open localdb: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = local.Close() })
|
|
|
|
cfg := &LocalConfiguration{
|
|
UUID: "dedup-cfg",
|
|
Name: "Dedup",
|
|
Items: LocalConfigItems{{LotName: "CPU_A", Quantity: 1, UnitPrice: 100}},
|
|
ServerCount: 1,
|
|
SyncStatus: "pending",
|
|
OriginalUsername: "tester",
|
|
IsActive: true,
|
|
}
|
|
if err := local.SaveConfiguration(cfg); err != nil {
|
|
t.Fatalf("save seed config: %v", err)
|
|
}
|
|
|
|
baseV1Data, err := BuildConfigurationSnapshot(cfg)
|
|
if err != nil {
|
|
t.Fatalf("build v1 snapshot: %v", err)
|
|
}
|
|
baseV1 := LocalConfigurationVersion{
|
|
ID: uuid.NewString(),
|
|
ConfigurationUUID: cfg.UUID,
|
|
VersionNo: 1,
|
|
Data: baseV1Data,
|
|
AppVersion: "test",
|
|
CreatedAt: time.Now(),
|
|
}
|
|
if err := local.DB().Create(&baseV1).Error; err != nil {
|
|
t.Fatalf("insert base v1: %v", err)
|
|
}
|
|
if err := local.DB().Model(&LocalConfiguration{}).
|
|
Where("uuid = ?", cfg.UUID).
|
|
Update("current_version_id", baseV1.ID).Error; err != nil {
|
|
t.Fatalf("set current_version_id to v1: %v", err)
|
|
}
|
|
|
|
v2 := LocalConfigurationVersion{
|
|
ID: uuid.NewString(),
|
|
ConfigurationUUID: cfg.UUID,
|
|
VersionNo: 2,
|
|
Data: baseV1.Data,
|
|
AppVersion: "test",
|
|
CreatedAt: time.Now().Add(1 * time.Second),
|
|
}
|
|
if err := local.DB().Create(&v2).Error; err != nil {
|
|
t.Fatalf("insert duplicate v2: %v", err)
|
|
}
|
|
|
|
modified := *cfg
|
|
modified.Items = LocalConfigItems{{LotName: "CPU_A", Quantity: 2, UnitPrice: 100}}
|
|
total := modified.Items.Total()
|
|
modified.TotalPrice = &total
|
|
modified.UpdatedAt = time.Now()
|
|
v3Data, err := BuildConfigurationSnapshot(&modified)
|
|
if err != nil {
|
|
t.Fatalf("build v3 snapshot: %v", err)
|
|
}
|
|
|
|
v3 := LocalConfigurationVersion{
|
|
ID: uuid.NewString(),
|
|
ConfigurationUUID: cfg.UUID,
|
|
VersionNo: 3,
|
|
Data: v3Data,
|
|
AppVersion: "test",
|
|
CreatedAt: time.Now().Add(2 * time.Second),
|
|
}
|
|
if err := local.DB().Create(&v3).Error; err != nil {
|
|
t.Fatalf("insert v3: %v", err)
|
|
}
|
|
|
|
v4 := LocalConfigurationVersion{
|
|
ID: uuid.NewString(),
|
|
ConfigurationUUID: cfg.UUID,
|
|
VersionNo: 4,
|
|
Data: v3Data,
|
|
AppVersion: "test",
|
|
CreatedAt: time.Now().Add(3 * time.Second),
|
|
}
|
|
if err := local.DB().Create(&v4).Error; err != nil {
|
|
t.Fatalf("insert duplicate v4: %v", err)
|
|
}
|
|
|
|
if err := local.DB().Model(&LocalConfiguration{}).
|
|
Where("uuid = ?", cfg.UUID).
|
|
Update("current_version_id", v4.ID).Error; err != nil {
|
|
t.Fatalf("point current_version_id to duplicate v4: %v", err)
|
|
}
|
|
|
|
if err := local.DB().Where("id = ?", "2026_02_19_configuration_versions_dedup_spec_price").
|
|
Delete(&LocalSchemaMigration{}).Error; err != nil {
|
|
t.Fatalf("delete dedup migration record: %v", err)
|
|
}
|
|
|
|
if err := runLocalMigrations(local.DB()); err != nil {
|
|
t.Fatalf("rerun local migrations: %v", err)
|
|
}
|
|
|
|
var versions []LocalConfigurationVersion
|
|
if err := local.DB().Where("configuration_uuid = ?", cfg.UUID).
|
|
Order("version_no ASC").
|
|
Find(&versions).Error; err != nil {
|
|
t.Fatalf("load versions after dedup: %v", err)
|
|
}
|
|
if len(versions) != 2 {
|
|
t.Fatalf("expected 2 versions after dedup, got %d", len(versions))
|
|
}
|
|
if versions[0].VersionNo != 1 || versions[1].VersionNo != 3 {
|
|
t.Fatalf("expected kept version numbers [1,3], got [%d,%d]", versions[0].VersionNo, versions[1].VersionNo)
|
|
}
|
|
|
|
var after LocalConfiguration
|
|
if err := local.DB().Where("uuid = ?", cfg.UUID).First(&after).Error; err != nil {
|
|
t.Fatalf("load config after dedup: %v", err)
|
|
}
|
|
if after.CurrentVersionID == nil || *after.CurrentVersionID != v3.ID {
|
|
t.Fatalf("expected current_version_id to point to kept latest version v3")
|
|
}
|
|
}
|
|
|
|
func TestRunLocalMigrationsBackfillsConfigurationLineNo(t *testing.T) {
|
|
dbPath := filepath.Join(t.TempDir(), "line_no_backfill.db")
|
|
|
|
local, err := New(dbPath)
|
|
if err != nil {
|
|
t.Fatalf("open localdb: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = local.Close() })
|
|
|
|
projectUUID := "project-line"
|
|
cfg1 := &LocalConfiguration{
|
|
UUID: "line-cfg-1",
|
|
ProjectUUID: &projectUUID,
|
|
Name: "Cfg 1",
|
|
Items: LocalConfigItems{},
|
|
SyncStatus: "pending",
|
|
OriginalUsername: "tester",
|
|
IsActive: true,
|
|
CreatedAt: time.Now().Add(-2 * time.Hour),
|
|
}
|
|
cfg2 := &LocalConfiguration{
|
|
UUID: "line-cfg-2",
|
|
ProjectUUID: &projectUUID,
|
|
Name: "Cfg 2",
|
|
Items: LocalConfigItems{},
|
|
SyncStatus: "pending",
|
|
OriginalUsername: "tester",
|
|
IsActive: true,
|
|
CreatedAt: time.Now().Add(-1 * time.Hour),
|
|
}
|
|
if err := local.SaveConfiguration(cfg1); err != nil {
|
|
t.Fatalf("save cfg1: %v", err)
|
|
}
|
|
if err := local.SaveConfiguration(cfg2); err != nil {
|
|
t.Fatalf("save cfg2: %v", err)
|
|
}
|
|
|
|
if err := local.DB().Model(&LocalConfiguration{}).Where("uuid IN ?", []string{cfg1.UUID, cfg2.UUID}).Update("line_no", 0).Error; err != nil {
|
|
t.Fatalf("reset line_no: %v", err)
|
|
}
|
|
if err := local.DB().Where("id = ?", "2026_02_19_local_config_line_no").Delete(&LocalSchemaMigration{}).Error; err != nil {
|
|
t.Fatalf("delete migration record: %v", err)
|
|
}
|
|
|
|
if err := runLocalMigrations(local.DB()); err != nil {
|
|
t.Fatalf("rerun local migrations: %v", err)
|
|
}
|
|
|
|
var rows []LocalConfiguration
|
|
if err := local.DB().Where("uuid IN ?", []string{cfg1.UUID, cfg2.UUID}).Order("created_at ASC").Find(&rows).Error; err != nil {
|
|
t.Fatalf("load configurations: %v", err)
|
|
}
|
|
if len(rows) != 2 {
|
|
t.Fatalf("expected 2 configurations, got %d", len(rows))
|
|
}
|
|
if rows[0].Line != 10 || rows[1].Line != 20 {
|
|
t.Fatalf("expected line_no [10,20], got [%d,%d]", rows[0].Line, rows[1].Line)
|
|
}
|
|
}
|