193 lines
6.1 KiB
Go
193 lines
6.1 KiB
Go
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
|
|
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
|
|
|
|
package server
|
|
|
|
// The billing views' environment switch and absence line
|
|
// (stripe-environment-stamp D7, tasks 6.4 and 6.5), pinned at the level
|
|
// they are decided: the line's exact wording in both states, the fact
|
|
// that nothing hidden renders nothing, the URL the switch points at, and
|
|
// which rows earn the badge.
|
|
|
|
import (
|
|
"database/sql"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// billingNav is one of the four billing views' navs, in the state a
|
|
// request would leave it: a search, a status facet, page two, and the
|
|
// environment switch carried on Extra.
|
|
func billingNav(all bool) ListNav {
|
|
return ListNav{
|
|
BasePath: "/operator/billing/invoices",
|
|
FacetParam: "status",
|
|
Q: "acme",
|
|
Facet: "open",
|
|
Page: 2,
|
|
Extra: EnvExtra(all),
|
|
}
|
|
}
|
|
|
|
func TestEnvironmentNoticeWording(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
filter billingEnvFilter
|
|
wantText string
|
|
wantLabel string
|
|
}{
|
|
{
|
|
name: "hidden rows name themselves and offer the switch",
|
|
filter: billingEnvFilter{KeyMode: "live", Hidden: 12},
|
|
wantText: "12 invoices from test mode are not shown.",
|
|
wantLabel: "Show all",
|
|
},
|
|
{
|
|
name: "one hidden row reads as one",
|
|
filter: billingEnvFilter{KeyMode: "live", Hidden: 1},
|
|
wantText: "1 invoice from test mode is not shown.",
|
|
wantLabel: "Show all",
|
|
},
|
|
{
|
|
name: "under a test key the other environment is live",
|
|
filter: billingEnvFilter{KeyMode: "test", Hidden: 3},
|
|
wantText: "3 invoices from live mode are not shown.",
|
|
wantLabel: "Show all",
|
|
},
|
|
{
|
|
name: "the all state names itself and offers the way back",
|
|
filter: billingEnvFilter{KeyMode: "live", Hidden: 12, All: true},
|
|
wantText: "Showing all environments.",
|
|
wantLabel: "Show live only",
|
|
},
|
|
{
|
|
name: "the all state under a test key",
|
|
filter: billingEnvFilter{KeyMode: "test", Hidden: 12, All: true},
|
|
wantText: "Showing all environments.",
|
|
wantLabel: "Show test only",
|
|
},
|
|
{
|
|
name: "nothing hidden renders no line",
|
|
filter: billingEnvFilter{KeyMode: "live"},
|
|
},
|
|
{
|
|
name: "an unclassified key hides nothing and says nothing",
|
|
filter: billingEnvFilter{Hidden: 12},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
n := environmentNotice(billingNav(tc.filter.All), tc.filter, "invoice", "invoices")
|
|
if n.Text != tc.wantText {
|
|
t.Errorf("Text = %q, want %q", n.Text, tc.wantText)
|
|
}
|
|
if n.LinkLabel != tc.wantLabel {
|
|
t.Errorf("LinkLabel = %q, want %q", n.LinkLabel, tc.wantLabel)
|
|
}
|
|
if n.Show() != (tc.wantText != "") {
|
|
t.Errorf("Show() = %v for text %q", n.Show(), n.Text)
|
|
}
|
|
if !n.Show() {
|
|
return
|
|
}
|
|
if strings.Contains(n.Text, "—") {
|
|
t.Errorf("the line uses an em dash: %q", n.Text)
|
|
}
|
|
// The switch keeps the search and the facet and returns to
|
|
// page one, because the state change re-windows the whole set.
|
|
if !strings.Contains(n.LinkHref, "q=acme") || !strings.Contains(n.LinkHref, "status=open") {
|
|
t.Errorf("LinkHref = %q, want it to keep the search and the facet", n.LinkHref)
|
|
}
|
|
if strings.Contains(n.LinkHref, "page=") {
|
|
t.Errorf("LinkHref = %q, want it to return to page one", n.LinkHref)
|
|
}
|
|
wantEnv := !tc.filter.All
|
|
if strings.Contains(n.LinkHref, "env=all") != wantEnv {
|
|
t.Errorf("LinkHref = %q, want env=all present: %v", n.LinkHref, wantEnv)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestEnvAllTravelsTheScaffold pins task 6.4: the parameter rides
|
|
// ListNav.Extra, so every URL the scaffold builds and every hidden input
|
|
// the search form emits carries it.
|
|
func TestEnvAllTravelsTheScaffold(t *testing.T) {
|
|
nav := billingNav(true)
|
|
nav.PerPageOptions = perPageOptions
|
|
|
|
for name, got := range map[string]string{
|
|
"next page": nav.NextURL(),
|
|
"previous page": nav.PrevURL(),
|
|
"facet": nav.FacetURL("paid"),
|
|
"clear search": nav.ClearSearchURL(),
|
|
"page size": nav.PerPageURL(10),
|
|
} {
|
|
if !strings.Contains(got, "env=all") {
|
|
t.Errorf("%s URL = %q, want it to carry env=all", name, got)
|
|
}
|
|
}
|
|
if got := nav.ExtraInputs(); got.Get("env") != "all" {
|
|
t.Errorf("search form hidden inputs = %v, want env=all", got)
|
|
}
|
|
|
|
// The ordinary state adds nothing, so a canonical view keeps a
|
|
// canonical URL.
|
|
plain := billingNav(false)
|
|
if strings.Contains(plain.NextURL(), "env=") {
|
|
t.Errorf("the ordinary state must add no env parameter, got %q", plain.NextURL())
|
|
}
|
|
if EnvExtra(false) != nil {
|
|
t.Error("the ordinary state carries no Extra")
|
|
}
|
|
}
|
|
|
|
func TestParseEnvAll(t *testing.T) {
|
|
for target, want := range map[string]bool{
|
|
"/operator/billing/invoices": false,
|
|
"/operator/billing/invoices?env=all": true,
|
|
"/operator/billing/invoices?env=live": false,
|
|
"/operator/billing/invoices?env=": false,
|
|
"/operator/billing/invoices?q=a&env=all": true,
|
|
} {
|
|
r := httptest.NewRequest(http.MethodGet, target, nil)
|
|
if got := ParseEnvAll(r); got != want {
|
|
t.Errorf("ParseEnvAll(%q) = %v, want %v", target, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestRowEnvState pins task 6.6: only a row from the environment the key
|
|
// is not in earns a badge, and only while the view shows both.
|
|
func TestRowEnvState(t *testing.T) {
|
|
live := sql.NullBool{Bool: true, Valid: true}
|
|
test := sql.NullBool{Bool: false, Valid: true}
|
|
unverified := sql.NullBool{}
|
|
|
|
cases := []struct {
|
|
name string
|
|
livemode sql.NullBool
|
|
keyMode string
|
|
all bool
|
|
want string
|
|
}{
|
|
{"test row under a live key, all state", test, "live", true, "test"},
|
|
{"live row under a test key, all state", live, "test", true, "live"},
|
|
{"a row in the key's own environment", live, "live", true, ""},
|
|
{"an unverified row", unverified, "live", true, ""},
|
|
{"the ordinary state badges nothing", test, "live", false, ""},
|
|
{"an unclassified key badges nothing", test, "", true, ""},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := rowEnvState(tc.livemode, tc.keyMode, tc.all); got != tc.want {
|
|
t.Errorf("rowEnvState = %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|