//go:build integration // Diese Tests laufen nur mit `go test -tags integration ./internal/opnsense/` // gegen eine echte OPNsense-Testinstanz. Sie bestätigen die in // docs/opnsense-api.md dokumentierten Annahmen über Feldnamen und Formate. package opnsense import ( "context" "io" "os" "testing" "time" ) func integrationClient(t *testing.T) *Client { t.Helper() base, key, secret := os.Getenv("OPNSENSE_URL"), os.Getenv("OPNSENSE_KEY"), os.Getenv("OPNSENSE_SECRET") if base == "" || key == "" || secret == "" { t.Skip("OPNSENSE_URL/OPNSENSE_KEY/OPNSENSE_SECRET nicht gesetzt") } c, err := New(Options{BaseURL: base, APIKey: key, APISecret: secret, CAFile: os.Getenv("OPNSENSE_CA"), Timeout: 20 * time.Second}) if err != nil { t.Fatal(err) } return c } // TestIntegrationProviders bestätigt Feldnamen und Map-Form der Provider-Antwort. func TestIntegrationProviders(t *testing.T) { ps, err := integrationClient(t).Providers(context.Background()) if err != nil { t.Fatalf("Providers: %v", err) } if len(ps) == 0 { t.Fatal("keine Provider — mindestens eine OpenVPN-Instanz muss exportierbar sein") } for _, p := range ps { if p.VPNID == "" || p.Name == "" { t.Errorf("unvollständiger Provider: %+v", p) } } } // TestIntegrationAccountsExpiryIsParsed bestätigt, dass das Ablaufdatum in // einem der unterstützten Formate ankommt und nicht stillschweigend leer bleibt. func TestIntegrationAccountsExpiryIsParsed(t *testing.T) { c := integrationClient(t) ps, err := c.Providers(context.Background()) if err != nil { t.Fatal(err) } var seen, withDate int for _, p := range ps { accs, err := c.Accounts(context.Background(), p.VPNID) if err != nil { t.Fatalf("Accounts(%s): %v", p.VPNID, err) } for _, a := range accs { seen++ if a.CommonName == "" { t.Errorf("Zertifikat %s ohne CommonName — Feldname prüfen: %+v", a.RefID, a) } if !a.ValidTo.IsZero() { withDate++ } } } if seen == 0 { t.Skip("keine Zertifikate auf der Testinstanz") } if withDate == 0 { t.Fatalf("kein einziges der %d Zertifikate hat ein auswertbares Ablaufdatum — "+ "flexTimeLayouts bzw. die Feldnamen in rawAccount müssen ergänzt werden", seen) } } // TestIntegrationAccountsRevokedField bestätigt, dass revozierte Zertifikate // als solche erkennbar sind. Voraussetzung: auf der Testinstanz existiert ein // revoziertes Zertifikat mit CN aus OPNSENSE_REVOKED_CN. func TestIntegrationAccountsRevokedField(t *testing.T) { wantCN := os.Getenv("OPNSENSE_REVOKED_CN") if wantCN == "" { t.Skip("OPNSENSE_REVOKED_CN nicht gesetzt") } c := integrationClient(t) ps, err := c.Providers(context.Background()) if err != nil { t.Fatal(err) } for _, p := range ps { accs, err := c.Accounts(context.Background(), p.VPNID) if err != nil { t.Fatalf("Accounts(%s): %v", p.VPNID, err) } for _, a := range accs { if a.CommonName == wantCN { if !a.Revoked { t.Fatalf("Zertifikat %q wird nicht als revoziert gemeldet: %+v", wantCN, a) } if a.IsUsable(time.Now()) { t.Fatalf("revoziertes Zertifikat %q gilt als nutzbar", wantCN) } return } } } t.Fatalf("CN %q auf keiner Instanz gefunden", wantCN) } // TestIntegrationExportFormats bestätigt, dass beide angebotenen Formate // nicht-leere Konfigurationen liefern. func TestIntegrationExportFormats(t *testing.T) { refID := os.Getenv("OPNSENSE_CERT_REF") vpnID := os.Getenv("OPNSENSE_VPNID") if refID == "" || vpnID == "" { t.Skip("OPNSENSE_CERT_REF/OPNSENSE_VPNID nicht gesetzt") } c := integrationClient(t) for _, format := range []string{FormatOVPN, FormatViscosity} { res, err := c.Export(context.Background(), vpnID, refID, format) if err != nil { t.Errorf("Export(%s): %v", format, err) continue } n, _ := io.Copy(io.Discard, res.Body) res.Body.Close() if n == 0 { t.Errorf("Export(%s) lieferte 0 Bytes", format) } } }