Files
zeekayandHanzo Dev 1977136f52 Go 1.27.1, and the code it can say more simply
Modules declare go 1.27.1 and the build images follow. go fix applied the
modernizers: min, max, slices.Sort, strings.Cut and range over int. omitzero
is not applied, since it changes what the JSON says.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-16 19:46:18 -07:00

713 lines
23 KiB
Go

// Conformance suite for the Blue-Sky state filings adapter.
//
// Covers the Registrar dispatch, EFD adapter (NASAA Electronic
// Filing Depository), and the five real state-portal adapters
// (FL, TX, NY, CA, MA). Drives every code path against httptest
// fixtures and asserts wire-level behavior:
//
// - registrar dispatches to the right adapter by state
// - EFD authenticate-then-submit token flow
// - EFD FileNoticeOfSale happy path + payload shape
// - EFD RenewNotice happy path
// - EFD GetFilingStatus + 404 → ErrFilingNotFound
// - state portal adapters: FL, TX, NY, CA, MA all submit & status
// - validation errors block submission
// - 429 + 5xx retried; sustained → ErrRateLimited
// - 4xx non-retryable → *APIError
// - fee schedule returns correct value per state (incl. scaled)
// - state portals support electronic when configured
// - renewals to a no-renewal portal → ErrNotImplemented
// - SupportedStates returns 50 + DC + DC sentinels
//
// All fixtures are httptest.Server. No production traffic.
//
// Source-of-design: Public-Spec
// Source-ref: http://nasaaefd.org
// Source-ref: http://nasaaefd.org/About/FormDStates
package bluesky
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"
"time"
)
// --- helpers ---
func sampleNotice() *NoticeFiling {
return &NoticeFiling{
FilingType: FilingNoticeOfSale,
Issuer: Issuer{
CIK: "0001234567",
EntityName: "Osage Group LLC",
EntityType: "Limited Liability Company",
JurisdictionOfIncorporation: "OK",
YearOfIncorporation: "2022",
PrimaryAddress: Address{
Street1: "1500 S Utica Ave Ste 400", City: "Tulsa",
State: "OK", PostalCode: "74104", Country: "US",
},
Phone: "+19137779708",
Email: "[email protected]",
},
Offering: OfferingDetails{
FederalExemption: "06c", // 506(b)
TotalOfferingAmount: 5_000_000,
AmountSoldInState: 250_000,
DateOfFirstSaleInState: time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
TypesOfSecurities: []string{"Equity"},
SECFileNumber: "021-987654",
SECAccessionNumber: "0001234567-26-000001",
},
Signature: Signature{
IssuerName: "Osage Group LLC", SignatureName: "H. Dupont",
NameOfSigner: "H. Dupont", SignatureTitle: "CEO",
SignatureDate: "2026-05-15",
},
}
}
func sampleRenewal() *RenewalFiling {
return &RenewalFiling{
OriginalFilingID: "EFD-NY-26-1001",
OriginalFilingDate: time.Date(2026, 4, 15, 0, 0, 0, 0, time.UTC),
Issuer: sampleNotice().Issuer,
UpdatedOffering: sampleNotice().Offering,
Signature: sampleNotice().Signature,
}
}
// efdMux is a configurable EFD mock that captures the last filing
// request body. Returns the canonical login + filing receipts.
type efdMux struct {
*http.ServeMux
loginCalls atomic.Int32
submitCalls atomic.Int32
renewCalls atomic.Int32
statusCalls atomic.Int32
lastFiling atomic.Value // *efdFilingRequest
respond func(w http.ResponseWriter, path string)
}
func newEFDMux(t *testing.T) *efdMux {
mux := &efdMux{ServeMux: http.NewServeMux()}
mux.HandleFunc("/Account/Login", func(w http.ResponseWriter, r *http.Request) {
mux.loginCalls.Add(1)
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"token":"test-token","expires":"`+time.Now().Add(time.Hour).Format(time.RFC3339)+`"}`)
})
mux.HandleFunc("/FormD/Submit", func(w http.ResponseWriter, r *http.Request) {
mux.submitCalls.Add(1)
if got := r.Header.Get("Authorization"); !strings.HasPrefix(got, "Bearer test-token") {
t.Errorf("submit missing/incorrect bearer: %q", got)
}
var req efdFilingRequest
body, _ := io.ReadAll(r.Body)
if err := json.Unmarshal(body, &req); err != nil {
t.Errorf("decode submit body: %v", err)
}
mux.lastFiling.Store(&req)
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"filingId":"EFD-%s-26-1001","status":"RECEIVED","state":%q,"fee":%f,"receivedAt":%q,"expiresAt":%q,"paymentId":"P-100"}`,
req.State, req.State, 250.0,
time.Now().UTC().Format(time.RFC3339),
time.Now().Add(365*24*time.Hour).UTC().Format(time.RFC3339))
})
mux.HandleFunc("/FormD/Renew", func(w http.ResponseWriter, r *http.Request) {
mux.renewCalls.Add(1)
var req efdRenewalRequest
body, _ := io.ReadAll(r.Body)
if err := json.Unmarshal(body, &req); err != nil {
t.Errorf("decode renewal body: %v", err)
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"filingId":"EFD-%s-26-1002","status":"RECEIVED","state":%q,"fee":%f}`,
req.State, req.State, 300.0)
})
mux.HandleFunc("/FormD/Status/", func(w http.ResponseWriter, r *http.Request) {
mux.statusCalls.Add(1)
id := strings.TrimPrefix(r.URL.Path, "/FormD/Status/")
if id == "missing" {
http.Error(w, `{"error":"not found"}`, http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"filingId":%q,"state":"NY","status":"ACCEPTED","filedAt":%q,"acceptedAt":%q,"fee":300.0}`,
id, time.Now().UTC().Format(time.RFC3339), time.Now().UTC().Format(time.RFC3339))
})
return mux
}
func newEFDRegistrar(t *testing.T, srv *httptest.Server) *Registrar {
t.Helper()
return NewRegistrar(Config{
EFD: EFDConfig{
BaseURL: srv.URL,
Username: "tester", Password: "secret", FirmCRD: "123456",
},
FL: StatePortalConfig{BaseURL: srv.URL, APIKey: "fl-key", AccountID: "fl-acct"},
TX: StatePortalConfig{BaseURL: srv.URL, APIKey: "tx-key", AccountID: "tx-acct"},
NY: StatePortalConfig{BaseURL: srv.URL, APIKey: "ny-key", AccountID: "ny-acct"},
CA: StatePortalConfig{BaseURL: srv.URL, APIKey: "ca-key", AccountID: "ca-acct"},
MA: StatePortalConfig{BaseURL: srv.URL, APIKey: "ma-key", AccountID: "ma-acct"},
UserAgent: "Lux Captable [email protected]",
MaxRetries: 2,
RetryBaseDelay: time.Millisecond,
RetryMaxDelay: 10 * time.Millisecond,
HTTPClient: srv.Client(),
})
}
// --- Registrar dispatch ---
func TestRegistrar_SupportedStates_HasAll51(t *testing.T) {
reg := NewRegistrar(Config{})
got := reg.SupportedStates()
if len(got) < 51 {
t.Fatalf("expected at least 51 states (50 + DC), got %d", len(got))
}
seen := map[State]bool{}
for _, s := range got {
seen[s] = true
}
for _, want := range []State{"AL", "CA", "DC", "FL", "TX", "NY", "MA", "WY"} {
if !seen[want] {
t.Errorf("missing state %q", want)
}
}
}
func TestRegistrar_UnknownState(t *testing.T) {
reg := NewRegistrar(Config{})
_, err := reg.FileNoticeOfSale(context.Background(), "ZZ", sampleNotice())
if !errors.Is(err, ErrUnsupportedState) {
t.Fatalf("expected ErrUnsupportedState, got %v", err)
}
}
func TestRegistrar_ValidationBlocksSubmission(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatal("submission must not be sent when validation fails")
}))
defer srv.Close()
reg := newEFDRegistrar(t, srv)
bad := sampleNotice()
bad.Issuer.EntityName = ""
_, err := reg.FileNoticeOfSale(context.Background(), "CO", bad)
if !errors.Is(err, ErrInvalidNotice) {
t.Fatalf("expected ErrInvalidNotice, got %v", err)
}
}
// --- EFD adapter ---
func TestEFD_FileNoticeOfSale_HappyPath(t *testing.T) {
mux := newEFDMux(t)
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
ack, err := reg.FileNoticeOfSale(context.Background(), "CO", sampleNotice())
if err != nil {
t.Fatalf("FileNoticeOfSale: %v", err)
}
if ack.FilingID == "" {
t.Fatalf("missing filing_id; ack=%+v", ack)
}
if ack.State != "CO" {
t.Fatalf("state = %q", ack.State)
}
if ack.Status != "RECEIVED" {
t.Fatalf("status = %q", ack.Status)
}
if mux.loginCalls.Load() != 1 {
t.Errorf("expected 1 login, got %d", mux.loginCalls.Load())
}
if mux.submitCalls.Load() != 1 {
t.Errorf("expected 1 submit, got %d", mux.submitCalls.Load())
}
got, _ := mux.lastFiling.Load().(*efdFilingRequest)
if got == nil || got.Issuer.EntityName != "Osage Group LLC" {
t.Fatalf("filing payload not captured / wrong: %+v", got)
}
if got.Offering.TotalOfferingAmount != 5_000_000 {
t.Errorf("offering amount = %v", got.Offering.TotalOfferingAmount)
}
}
func TestEFD_TokenReused(t *testing.T) {
mux := newEFDMux(t)
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
for i := range 3 {
if _, err := reg.FileNoticeOfSale(context.Background(), "CO", sampleNotice()); err != nil {
t.Fatalf("call %d: %v", i, err)
}
}
if mux.loginCalls.Load() != 1 {
t.Errorf("expected 1 login across 3 submits, got %d", mux.loginCalls.Load())
}
}
func TestEFD_FormDXMLAttached(t *testing.T) {
mux := newEFDMux(t)
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
n := sampleNotice()
n.FormDXML = []byte(`<edgarSubmission><submissionType>D</submissionType></edgarSubmission>`)
if _, err := reg.FileNoticeOfSale(context.Background(), "CO", n); err != nil {
t.Fatalf("FileNoticeOfSale: %v", err)
}
got, _ := mux.lastFiling.Load().(*efdFilingRequest)
if got == nil || got.FormDXML == "" {
t.Fatalf("expected base64 Form D XML attached; got %+v", got)
}
}
func TestEFD_RenewNotice(t *testing.T) {
mux := newEFDMux(t)
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
ack, err := reg.RenewNotice(context.Background(), "CO", sampleRenewal())
if err != nil {
t.Fatalf("RenewNotice: %v", err)
}
if ack.FilingID == "" || ack.Status == "" {
t.Fatalf("bad ack: %+v", ack)
}
if mux.renewCalls.Load() != 1 {
t.Errorf("expected 1 renew call, got %d", mux.renewCalls.Load())
}
}
func TestEFD_RenewNotice_MissingOriginalID(t *testing.T) {
mux := newEFDMux(t)
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
r := sampleRenewal()
r.OriginalFilingID = ""
_, err := reg.RenewNotice(context.Background(), "CO", r)
if !errors.Is(err, ErrInvalidNotice) {
t.Fatalf("expected ErrInvalidNotice, got %v", err)
}
}
func TestEFD_GetFilingStatus_HappyPath(t *testing.T) {
mux := newEFDMux(t)
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
st, err := reg.GetFilingStatus(context.Background(), "CO", "EFD-CO-26-1001")
if err != nil {
t.Fatalf("GetFilingStatus: %v", err)
}
if st.Status != "ACCEPTED" {
t.Fatalf("status = %q", st.Status)
}
}
func TestEFD_GetFilingStatus_NotFound(t *testing.T) {
mux := newEFDMux(t)
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
_, err := reg.GetFilingStatus(context.Background(), "CO", "missing")
if !errors.Is(err, ErrFilingNotFound) {
t.Fatalf("expected ErrFilingNotFound, got %v", err)
}
}
func TestEFD_GetFilingStatus_EmptyID(t *testing.T) {
reg := NewRegistrar(Config{})
_, err := reg.GetFilingStatus(context.Background(), "CO", "")
if err == nil || !strings.Contains(err.Error(), "filing_id") {
t.Fatalf("expected filing_id error, got %v", err)
}
}
// --- Rate-limit / retry ---
func TestEFD_RetriesOn429(t *testing.T) {
var attempts atomic.Int32
mux := http.NewServeMux()
mux.HandleFunc("/Account/Login", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"token":"t","expires":"`+time.Now().Add(time.Hour).Format(time.RFC3339)+`"}`)
})
mux.HandleFunc("/FormD/Submit", func(w http.ResponseWriter, r *http.Request) {
if attempts.Add(1) == 1 {
w.Header().Set("Retry-After", "0")
http.Error(w, "throttled", http.StatusTooManyRequests)
return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"EFD-CO-26-1099","status":"RECEIVED","state":"CO","fee":75}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
if _, err := reg.FileNoticeOfSale(context.Background(), "CO", sampleNotice()); err != nil {
t.Fatalf("FileNoticeOfSale: %v", err)
}
if attempts.Load() < 2 {
t.Fatalf("expected at least 2 submit attempts, got %d", attempts.Load())
}
}
func TestEFD_SustainedRateLimit(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/Account/Login", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"token":"t","expires":"`+time.Now().Add(time.Hour).Format(time.RFC3339)+`"}`)
})
mux.HandleFunc("/FormD/Submit", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Retry-After", "0")
http.Error(w, "throttled", http.StatusTooManyRequests)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
_, err := reg.FileNoticeOfSale(context.Background(), "CO", sampleNotice())
if !errors.Is(err, ErrRateLimited) {
t.Fatalf("expected ErrRateLimited, got %v", err)
}
}
func TestEFD_NonRetryable4xx(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/Account/Login", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"token":"t","expires":"`+time.Now().Add(time.Hour).Format(time.RFC3339)+`"}`)
})
mux.HandleFunc("/FormD/Submit", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, `{"error":"bad request"}`, http.StatusBadRequest)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
_, err := reg.FileNoticeOfSale(context.Background(), "CO", sampleNotice())
var apiErr *APIError
if !errors.As(err, &apiErr) || apiErr.StatusCode != http.StatusBadRequest {
t.Fatalf("expected 400 APIError, got %v", err)
}
}
// --- State portal adapters ---
func TestStatePortal_FL_Submit(t *testing.T) {
mux := http.NewServeMux()
var got atomic.Value
mux.HandleFunc("/forms/d/submit", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer fl-key" {
t.Errorf("FL missing api key: %q", r.Header.Get("Authorization"))
}
var req portalFilingRequest
body, _ := io.ReadAll(r.Body)
_ = json.Unmarshal(body, &req)
got.Store(&req)
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"FL-26-1001","status":"RECEIVED","fee":200,"receivedAt":"2026-05-15T19:00:00Z"}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
ack, err := reg.FileNoticeOfSale(context.Background(), "FL", sampleNotice())
if err != nil {
t.Fatalf("FL submit: %v", err)
}
if ack.FilingID != "FL-26-1001" || ack.State != "FL" {
t.Fatalf("FL ack: %+v", ack)
}
if g, _ := got.Load().(*portalFilingRequest); g == nil || g.State != "FL" {
t.Fatalf("FL payload state = %+v", g)
}
}
func TestStatePortal_TX_Submit(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/notice/submit", func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") != "Bearer tx-key" {
t.Errorf("TX missing api key: %q", r.Header.Get("Authorization"))
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"TX-26-1001","status":"RECEIVED","fee":500}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
ack, err := reg.FileNoticeOfSale(context.Background(), "TX", sampleNotice())
if err != nil {
t.Fatalf("TX submit: %v", err)
}
if ack.FilingID != "TX-26-1001" {
t.Fatalf("TX ack: %+v", ack)
}
}
func TestStatePortal_NY_SubmitAndRenew(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/securities/notice/submit", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"NY-26-1001","status":"RECEIVED","fee":1200}`)
})
mux.HandleFunc("/securities/notice/renew", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"NY-26-1002","status":"RECEIVED","fee":1200}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
if _, err := reg.FileNoticeOfSale(context.Background(), "NY", sampleNotice()); err != nil {
t.Fatalf("NY submit: %v", err)
}
if _, err := reg.RenewNotice(context.Background(), "NY", sampleRenewal()); err != nil {
t.Fatalf("NY renew: %v", err)
}
}
func TestStatePortal_CA_StatusAndAmend(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/securities/25102f/submit", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"CA-26-1001","status":"FILED","fee":275}`)
})
mux.HandleFunc("/securities/25102f/CA-26-1001", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"CA-26-1001","state":"CA","status":"ACCEPTED","fee":275,"filedAt":"2026-05-15T19:00:00Z","acceptedAt":"2026-05-16T09:00:00Z"}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
ack, err := reg.FileNoticeOfSale(context.Background(), "CA", sampleNotice())
if err != nil {
t.Fatalf("CA submit: %v", err)
}
if ack.FilingID != "CA-26-1001" || ack.Status != "FILED" {
t.Fatalf("CA ack: %+v", ack)
}
st, err := reg.GetFilingStatus(context.Background(), "CA", "CA-26-1001")
if err != nil {
t.Fatalf("CA status: %v", err)
}
if st.Status != "ACCEPTED" {
t.Fatalf("CA status = %q", st.Status)
}
}
func TestStatePortal_MA_Submit(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/sct/notice/submit", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"filingId":"MA-26-1001","status":"RECEIVED","fee":300}`)
})
srv := httptest.NewServer(mux)
defer srv.Close()
reg := newEFDRegistrar(t, srv)
ack, err := reg.FileNoticeOfSale(context.Background(), "MA", sampleNotice())
if err != nil {
t.Fatalf("MA submit: %v", err)
}
if ack.FilingID != "MA-26-1001" {
t.Fatalf("MA ack: %+v", ack)
}
}
func TestStatePortal_FL_NoRenewals(t *testing.T) {
reg := newEFDRegistrar(t, httptest.NewServer(http.NewServeMux()))
_, err := reg.RenewNotice(context.Background(), "FL", sampleRenewal())
if !errors.Is(err, ErrNotImplemented) {
t.Fatalf("expected ErrNotImplemented, got %v", err)
}
}
// --- Fee schedule ---
func TestCalculateStateFee_EFDStates(t *testing.T) {
reg := NewRegistrar(Config{})
cases := map[State]float64{
"AL": 300, "AK": 600, "CO": 75, "DE": 200, "DC": 250,
"GA": 250, "ID": 50, "IN": 100, "MN": 50, "WY": 200,
}
for state, want := range cases {
fee, err := reg.CalculateStateFee(state, sampleNotice())
if err != nil {
t.Errorf("%s: %v", state, err)
continue
}
if fee.StateFee != want {
t.Errorf("%s state fee = %v, want %v", state, fee.StateFee, want)
}
if fee.SystemFee != EFDSystemFeeUSD {
t.Errorf("%s missing EFD system fee", state)
}
if fee.Method != "ACH" {
t.Errorf("%s method = %q", state, fee.Method)
}
}
}
func TestCalculateStateFee_ScaledStates(t *testing.T) {
reg := NewRegistrar(Config{})
// CA — $25 + 0.001% of offering, max $300.
n := sampleNotice()
n.Offering.TotalOfferingAmount = 100_000_000
fee, err := reg.CalculateStateFee("CA", n)
if err != nil {
t.Fatalf("CA: %v", err)
}
if fee.StateFee != 300 {
t.Errorf("CA scaled fee = %v, want 300 cap", fee.StateFee)
}
// NY — $300 ≤ $500K, $1,200 > $500K.
small := sampleNotice()
small.Offering.TotalOfferingAmount = 250_000
if f, _ := reg.CalculateStateFee("NY", small); f.StateFee != 300 {
t.Errorf("NY small fee = %v, want 300", f.StateFee)
}
if f, _ := reg.CalculateStateFee("NY", n); f.StateFee != 1200 {
t.Errorf("NY large fee = %v, want 1200", f.StateFee)
}
// WA — $300 + 0.1% on amount > $50K, max $1,500.
mid := sampleNotice()
mid.Offering.TotalOfferingAmount = 500_000
wa, _ := reg.CalculateStateFee("WA", mid)
expectedWA := 300.0 + 0.001*(500_000-50_000)
if wa.StateFee != expectedWA {
t.Errorf("WA scaled fee = %v, want %v", wa.StateFee, expectedWA)
}
}
func TestCalculateStateFee_PortalStates(t *testing.T) {
reg := NewRegistrar(Config{})
fl, err := reg.CalculateStateFee("FL", sampleNotice())
if err != nil {
t.Fatalf("FL: %v", err)
}
if fl.Method != "Check" {
t.Errorf("FL method = %q, want Check (paper portal)", fl.Method)
}
if fl.SystemFee != 0 {
t.Errorf("FL system fee should be 0 (no EFD), got %v", fl.SystemFee)
}
if fl.StateFee != 200 {
t.Errorf("FL state fee = %v", fl.StateFee)
}
tx, _ := reg.CalculateStateFee("TX", sampleNotice())
if tx.Method != "Check" || tx.StateFee != 500 {
t.Errorf("TX fee = %+v", tx)
}
}
func TestCalculateStateFee_UnknownState(t *testing.T) {
reg := NewRegistrar(Config{})
_, err := reg.CalculateStateFee("ZZ", sampleNotice())
if !errors.Is(err, ErrUnsupportedState) {
t.Fatalf("expected ErrUnsupportedState, got %v", err)
}
}
// --- Validation ---
func TestValidate_MissingFederalExemption(t *testing.T) {
n := sampleNotice()
n.Offering.FederalExemption = ""
if err := validateNoticeFiling(n); err == nil || !strings.Contains(err.Error(), "federal_exemption") {
t.Fatalf("expected federal_exemption error, got %v", err)
}
}
func TestValidate_MissingTotalOffering(t *testing.T) {
n := sampleNotice()
n.Offering.TotalOfferingAmount = 0
if err := validateNoticeFiling(n); err == nil || !strings.Contains(err.Error(), "total_offering_amount") {
t.Fatalf("expected total_offering_amount error, got %v", err)
}
}
func TestValidate_BadSignatureDate(t *testing.T) {
n := sampleNotice()
n.Signature.SignatureDate = "May 15 2026"
if err := validateNoticeFiling(n); err == nil || !strings.Contains(err.Error(), "YYYY-MM-DD") {
t.Fatalf("expected date format error, got %v", err)
}
}
// --- RegisterAdapter override ---
type fakeAdapter struct {
state State
called atomic.Bool
}
func (f *fakeAdapter) State() State { return f.state }
func (f *fakeAdapter) SupportsElectronic() bool { return true }
func (f *fakeAdapter) FileNoticeOfSale(_ context.Context, _ *NoticeFiling) (*Acknowledgment, error) {
f.called.Store(true)
return &Acknowledgment{FilingID: "FAKE-1", State: f.state, Status: "RECEIVED"}, nil
}
func (f *fakeAdapter) RenewNotice(_ context.Context, _ *RenewalFiling) (*Acknowledgment, error) {
return &Acknowledgment{FilingID: "FAKE-R", State: f.state, Status: "RECEIVED"}, nil
}
func (f *fakeAdapter) GetFilingStatus(_ context.Context, _ string) (*FilingStatus, error) {
return &FilingStatus{FilingID: "FAKE-1", State: f.state, Status: "ACCEPTED"}, nil
}
func (f *fakeAdapter) CalculateFee(_ *NoticeFiling) (FeeAmount, error) {
return FeeAmount{State: f.state, StateFee: 1.0, TotalDue: 1.0, Currency: "USD", Method: "ACH"}, nil
}
func TestRegistrar_RegisterAdapter_Override(t *testing.T) {
reg := NewRegistrar(Config{})
fake := &fakeAdapter{state: "CO"}
reg.RegisterAdapter("CO", fake)
ack, err := reg.FileNoticeOfSale(context.Background(), "CO", sampleNotice())
if err != nil {
t.Fatalf("override submit: %v", err)
}
if !fake.called.Load() {
t.Fatalf("fake adapter not called")
}
if ack.FilingID != "FAKE-1" {
t.Fatalf("ack: %+v", ack)
}
}
// --- parseRetryAfter sanity ---
func TestParseRetryAfter_Forms(t *testing.T) {
if got := parseRetryAfter(""); got != 0 {
t.Fatalf("empty: %v", got)
}
if got := parseRetryAfter("3"); got != 3*time.Second {
t.Fatalf("3: %v", got)
}
}
// --- base64Std (internal helper) ---
func TestBase64Std(t *testing.T) {
cases := map[string]string{
"": "",
"f": "Zg==",
"fo": "Zm8=",
"foo": "Zm9v",
"foob": "Zm9vYg==",
}
for in, want := range cases {
if got := base64Std([]byte(in)); got != want {
t.Errorf("base64Std(%q) = %q, want %q", in, got, want)
}
}
}