package handlers import ( "net/http" "net/http/httptest" "strings" "testing" bloomtypes "github.com/oy/openyield/x/bloom/types" ) func TestBloomSeededRecordRendersTargetRate(t *testing.T) { srv := newTestServer(t) mux := http.NewServeMux() srv.Register(mux) rec := httptest.NewRecorder() req := httptest.NewRequest("GET", "/bloom/stash-holder-alia", nil) mux.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("GET /bloom/stash-holder-alia: status %d, want 200", rec.Code) } body := rec.Body.String() // Accrued Grain present. if !strings.Contains(body, "Grain") { t.Errorf("body missing 'Grain'") } // Target rate 4.5% (from TargetBloomRateBasisPoints=450). want := formatFloat(float64(bloomtypes.TargetBloomRateBasisPoints) / 100) if !strings.Contains(body, want) { t.Errorf("body missing target rate %s%% (TargetBloomRateBasisPoints=%d)", want, bloomtypes.TargetBloomRateBasisPoints) } // Mission Lock note present. if !strings.Contains(body, "real production") { t.Errorf("body missing Mission Lock note about real production") } assertNoBannedTerms(t, body) } func TestBloomMissingReturns404(t *testing.T) { srv := newTestServer(t) mux := http.NewServeMux() srv.Register(mux) rec := httptest.NewRecorder() req := httptest.NewRequest("GET", "/bloom/stash-nobody", nil) mux.ServeHTTP(rec, req) if rec.Code != http.StatusNotFound { t.Fatalf("GET /bloom/stash-nobody: status %d, want 404", rec.Code) } // G-026: rendered-HTML lexicon check on the ERROR response body too. assertNoBannedTerms(t, rec.Body.String()) } // TestBloomTargetRateFromCodeConstant (D-073 regression guard): asserts the // handler reads x/bloom/types.TargetBloomRateBasisPoints=450 (NOT a hardcoded // 450 or a docs value). This test would FAIL if the handler hardcoded the rate // instead of reading the code constant. func TestBloomTargetRateFromCodeConstant(t *testing.T) { // D-073: the code constant is the source of truth. if bloomtypes.TargetBloomRateBasisPoints != 450 { t.Fatalf("D-073: TargetBloomRateBasisPoints = %d, want 450 (code constant)", bloomtypes.TargetBloomRateBasisPoints) } if bloomtypes.MinBloomRateBasisPoints != 400 { t.Fatalf("D-073: MinBloomRateBasisPoints = %d, want 400 (code constant)", bloomtypes.MinBloomRateBasisPoints) } if bloomtypes.MaxBloomRateBasisPoints != 500 { t.Fatalf("D-073: MaxBloomRateBasisPoints = %d, want 500 (code constant)", bloomtypes.MaxBloomRateBasisPoints) } srv := newTestServer(t) mux := http.NewServeMux() srv.Register(mux) rec := httptest.NewRecorder() req := httptest.NewRequest("GET", "/bloom/stash-holder-alia", nil) mux.ServeHTTP(rec, req) body := rec.Body.String() // The rendered target rate must be the code constant / 100 = 4.5. wantTarget := formatFloat(float64(bloomtypes.TargetBloomRateBasisPoints) / 100) if !strings.Contains(body, wantTarget) { t.Errorf("D-073: body missing target rate %s%% (from code constant %d)", wantTarget, bloomtypes.TargetBloomRateBasisPoints) } // The seeded record for holder-alia uses RateBasisPoints=450 (the target). rec2, ok := srv.Store.GetBloomRecord("stash-holder-alia") if !ok { t.Fatal("seeded bloom record stash-holder-alia missing") } if rec2.RateBasisPoints != bloomtypes.TargetBloomRateBasisPoints { t.Errorf("D-073: seeded record RateBasisPoints = %d, want %d (code constant)", rec2.RateBasisPoints, bloomtypes.TargetBloomRateBasisPoints) } // The rate band must be rendered from the code constants. wantMin := formatFloat(float64(bloomtypes.MinBloomRateBasisPoints) / 100) wantMax := formatFloat(float64(bloomtypes.MaxBloomRateBasisPoints) / 100) if !strings.Contains(body, wantMin) { t.Errorf("D-073: body missing min rate %s%% (from code constant)", wantMin) } if !strings.Contains(body, wantMax) { t.Errorf("D-073: body missing max rate %s%% (from code constant)", wantMax) } } // Compile-time assertion that the handler uses the real x/bloom/types struct. var _ bloomtypes.BloomRecord // formatFloat formats a float to 1 decimal place without importing strconv // (keeps the test deps minimal; matches the template's printf "%.1f"). func formatFloat(f float64) string { // Round to 1 decimal. rounded := float64(int(f*10+0.5)) / 10 whole := int(rounded) frac := int((rounded - float64(whole)) * 10) if frac == 0 { return formatInt2(int64(whole)) + ".0" } return formatInt2(int64(whole)) + "." + string(rune('0'+frac)) } func formatInt2(n int64) string { if n == 0 { return "0" } neg := n < 0 if neg { n = -n } var buf [20]byte i := len(buf) for n > 0 { i-- buf[i] = byte('0' + n%10) n /= 10 } if neg { i-- buf[i] = '-' } return string(buf[i:]) }