Files
member-console/internal/server/list_scale_environment_test.go
T

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)
}
})
}
}