package offsite import ( "context" "errors" "net" "strings" "testing" ) // R-250 (operator ruling 2026-10-12, S29, `09` §3 decision 224): a fresh Storage Box name resolves // AAAA-first, and the hub pod has no IPv6 route — measured 2026-08-07: `dial tcp [2a01:…]:23: connect: // network is unreachable` while the A record already worked. The scan dials IPv4 FIRST and falls back to // any family only when IPv4 fails. // RED-PROOF: with the old single `DialContext(ctx, "tcp", …)` the first network recorded is "tcp". func TestScannerDialsIPv4First(t *testing.T) { var nets []string s := SSHHostKeyScanner{dial: func(_ context.Context, network, _ string) (net.Conn, error) { nets = append(nets, network) return nil, errors.New("refused in test") }} if _, err := s.Fingerprint(context.Background(), "box.example", 23); err == nil { t.Fatal("a failed dial must fail the scan") } if len(nets) != 2 || nets[0] != "tcp4" || nets[1] != "tcp" { t.Fatalf("dial order = %v, want [tcp4 tcp]", nets) } } // R-250 second half: when the scan runs out, the operator is told it is safe to press again, and a // second press really creates nothing twice. RED-PROOF: with the old wrap ("offsite: host-key scan …") // errors.Is fails and the text has no „SAFE to press". func TestScanOutOfRetriesSaysSafeToPressAgain(t *testing.T) { p, fake, _ := newTestProvisioner(t) p.Scanner = &fakeScanner{err: errors.New("dial tcp: lookup box: no such host")} _, err := p.ProvisionOffsite(context.Background(), "cust-r250", Input{Enabled: true, Type: "shared", QuotaGB: 10}) if !errors.Is(err, ErrScanNotYetReachable) || !strings.Contains(err.Error(), "SAFE to press Save again") { t.Fatalf("err = %v, want the safe-to-press-again error", err) } n := len(fake.Subaccounts) p.Scanner = &fakeScanner{fp: "SHA256:ok"} if _, err := p.ProvisionOffsite(context.Background(), "cust-r250", Input{Enabled: true, Type: "shared", QuotaGB: 10}); err != nil { t.Fatalf("second press: %v", err) } if len(fake.Subaccounts) != n { t.Fatalf("the second press created another sub-account (%d → %d)", n, len(fake.Subaccounts)) } }