You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

101 lines
3.6 KiB

package mock
import (
"context"
"path/filepath"
"testing"
"time"
"tera-clone/services/tera-api/internal/domain"
)
// Exercise the production retention switch at the storage boundary, not a UI
// filter. Existing snapshots remain readable while new observations only append
// transitions, including after the saved configuration is loaded again.
func TestLinkChangesOnlyPreservesHistoryAndSurvivesReload(t *testing.T) {
for _, journalMode := range []bool{false, true} {
name := "jsonl"
if journalMode {
name = "journal"
}
t.Run(name, func(t *testing.T) {
ctx := context.Background()
dir := t.TempDir()
options := StoreOptions{ConfigPath: filepath.Join(dir, "config.json")}
if journalMode {
options.LogJournal = &memoryJournal{}
} else {
options.LinkLogPath = filepath.Join(dir, "links.jsonl")
}
store, err := NewStoreWithOptions(fixedClock, options)
if err != nil {
t.Fatal(err)
}
defer store.Close()
observation := domain.LinkStateObservation{
Kind: "serial", Resource: "CH01", Signal: "cr", State: "up", ObservedAt: fixedClock(),
}
first, err := store.RecordLinkState(ctx, observation)
if err != nil || first == nil || first.EventType != "snapshot" {
t.Fatalf("historical snapshot = %+v, %v", first, err)
}
before, err := store.LogRetention(ctx)
if err != nil {
t.Fatal(err)
}
disabled := false
if _, err := store.UpdateLogRetention(ctx, domain.LogRetentionUpdate{AllPortStatusEnabled: &disabled}); err != nil {
t.Fatal(err)
}
if _, err := store.SaveAndApplyConfig(ctx); err != nil {
t.Fatal(err)
}
want := before
want.AllPortStatusEnabled = false
if after, err := store.LogRetention(ctx); err != nil || after != want {
t.Fatalf("only snapshot setting should change: got %+v, err=%v", after, err)
}
for index, state := range []string{"up", "down", "down", "up", "up"} {
observation.State = state
observation.ObservedAt = fixedClock().Add(time.Duration(index+1) * time.Hour)
event, err := store.RecordLinkState(ctx, observation)
if err != nil {
t.Fatal(err)
}
changed := index == 1 || index == 3
if !changed && event != nil {
t.Fatalf("unchanged state stored after snapshot interval: %+v", event)
}
if changed && (event == nil || event.EventType != "transition" || event.PreviousState == event.State) {
t.Fatalf("state change was not recorded: %+v", event)
}
}
// An unseen, unavailable port establishes a baseline without logging.
baseline := domain.LinkStateObservation{
Kind: "serial", Resource: "CH02", Signal: "unknown", State: "unknown", ObservedAt: fixedClock(),
}
if event, err := store.RecordLinkState(ctx, baseline); err != nil || event != nil {
t.Fatalf("new baseline must not be stored: %+v, %v", event, err)
}
reloaded, err := NewStoreWithOptions(fixedClock, options)
if err != nil {
t.Fatal(err)
}
defer reloaded.Close()
if after, err := reloaded.LogRetention(ctx); err != nil || after != want {
t.Fatalf("saved retention not preserved: %+v, %v", after, err)
}
observation.ObservedAt = fixedClock().Add(6 * time.Hour)
if event, err := reloaded.RecordLinkState(ctx, observation); err != nil || event != nil {
t.Fatalf("reload must not resume same-state snapshots: %+v, %v", event, err)
}
for eventType, count := range map[string]int{"snapshot": 1, "transition": 2} {
events, err := reloaded.LinkLogEvents(ctx, domain.LinkLogFilter{EventType: eventType})
if err != nil || len(events) != count {
t.Fatalf("%s records = %d, want %d, err=%v", eventType, len(events), count, err)
}
}
})
}
}