package db import ( "errors" "reflect" "testing" "cloudipvalidator/internal/config" ) func TestBootstrapInboundChecksSeedsOnceFromConfig(t *testing.T) { d, ctx := newTestDB(t) cfg := &config.ControlAPI{Inbound: config.InboundConfig{Ports: []int{22, 80}, ICMP: true}} if err := d.BootstrapFromConfig(ctx, cfg); err != nil { t.Fatalf("first bootstrap: %v", err) } settings, err := d.GetInboundChecks(ctx) if err != nil { t.Fatalf("get inbound checks: %v", err) } if !reflect.DeepEqual(settings.Ports, []int{22, 80}) || !settings.ICMP { t.Fatalf("expected seeded {[22 80] true}, got %+v", settings) } // A second bootstrap with a different YAML value must not overwrite the // now-non-empty table — same "DB is source of truth once seeded" // semantics as validators/sites/targets/check_types/settings. cfg2 := &config.ControlAPI{Inbound: config.InboundConfig{Ports: []int{443}, ICMP: false}} if err := d.BootstrapFromConfig(ctx, cfg2); err != nil { t.Fatalf("second bootstrap: %v", err) } settings, err = d.GetInboundChecks(ctx) if err != nil { t.Fatalf("get inbound checks after second bootstrap: %v", err) } if !reflect.DeepEqual(settings.Ports, []int{22, 80}) || !settings.ICMP { t.Fatalf("expected YAML to be ignored on non-empty table, got %+v", settings) } } func TestSetInboundChecksRejectsPortOutOfRange(t *testing.T) { d, ctx := newTestDB(t) if err := d.BootstrapFromConfig(ctx, &config.ControlAPI{}); err != nil { t.Fatalf("bootstrap: %v", err) } for _, p := range []int{0, -1, 65536, 70000} { if err := d.SetInboundChecks(ctx, []int{p}, false); !errors.Is(err, ErrValidation) { t.Fatalf("port %d: expected ErrValidation, got %v", p, err) } } } func TestSetInboundChecksRejectsDuplicatePorts(t *testing.T) { d, ctx := newTestDB(t) if err := d.BootstrapFromConfig(ctx, &config.ControlAPI{}); err != nil { t.Fatalf("bootstrap: %v", err) } if err := d.SetInboundChecks(ctx, []int{22, 80, 22}, false); !errors.Is(err, ErrValidation) { t.Fatalf("expected ErrValidation for duplicate port, got %v", err) } } func TestGetSetInboundChecksRoundTrip(t *testing.T) { d, ctx := newTestDB(t) if err := d.BootstrapFromConfig(ctx, &config.ControlAPI{}); err != nil { t.Fatalf("bootstrap: %v", err) } before, err := d.GetInboundChecks(ctx) if err != nil { t.Fatalf("get inbound checks: %v", err) } if len(before.Ports) != 0 || before.ICMP { t.Fatalf("expected default {[] false}, got %+v", before) } if err := d.SetInboundChecks(ctx, []int{22, 443, 8080}, true); err != nil { t.Fatalf("set inbound checks: %v", err) } after, err := d.GetInboundChecks(ctx) if err != nil { t.Fatalf("get inbound checks after set: %v", err) } if !reflect.DeepEqual(after.Ports, []int{22, 443, 8080}) || !after.ICMP { t.Fatalf("expected {[22 443 8080] true}, got %+v", after) } if after.UpdatedAt.Before(before.UpdatedAt) { t.Fatalf("expected updated_at not to go backwards, before=%v after=%v", before.UpdatedAt, after.UpdatedAt) } // Setting an empty port list + icmp:false is legal — it's the "disable // all inbound checks without touching sites" configuration. if err := d.SetInboundChecks(ctx, nil, false); err != nil { t.Fatalf("set empty inbound checks: %v", err) } cleared, err := d.GetInboundChecks(ctx) if err != nil { t.Fatalf("get inbound checks after clear: %v", err) } if len(cleared.Ports) != 0 || cleared.ICMP { t.Fatalf("expected {[] false} after clearing, got %+v", cleared) } }