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
101 lines
3.6 KiB
|
2 months ago
|
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)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|