package ecoretest
import (
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/vaughan0/go-ini"
"sourcecraft.dev/bigbes/sr-ht-core/crypto"
"sourcecraft.dev/bigbes/sr-ht-ecore/chrome"
)
// TestConfigHasTheSectionsTheNavRulesNeed runs the config through the very
// consumer it exists for. Every rule of the switcher — canonical order first,
// customs alphabetical after, hub/paste/pages excluded, a configured service
// without an origin skipped — needs a section in the fixture to be exercised at
// all, and the copies this package replaces were each missing a different one.
func TestConfigHasTheSectionsTheNavRulesNeed(t *testing.T) {
nav := chrome.BuildNav(Config("bench.sr.ht"), "bench.sr.ht")
var names []string
for _, item := range nav {
names = append(names, item.Name)
}
assert.Equal(t, []string{
"git", "lists", "todo", "builds", "man", "meta",
"bench", "cov", "diff", "dolt", "spec", "tokens",
}, names)
for _, item := range nav {
assert.Equal(t, item.Name == "bench", item.Active, "active flag for %s", item.Name)
assert.Equal(t, Origin(item.Name+".sr.ht"), item.Origin)
}
}
// TestConfigCarriesTheServiceItIsBuiltFor: a service this package has never
// heard of still gets a config it appears in.
func TestConfigCarriesTheServiceItIsBuiltFor(t *testing.T) {
conf := Config("newthing.sr.ht")
origin, ok := conf.Get("newthing.sr.ht", "origin")
require.True(t, ok, "an unknown service section must be added")
assert.Equal(t, "https://newthing.example", origin)
svc := chrome.NewService(conf, "newthing.sr.ht")
assert.Equal(t, "https://newthing.example", svc.SelfOrigin())
assert.Equal(t, SiteName, svc.SiteName())
assert.Equal(t, "https://meta.example", svc.MetaOrigin())
assert.Equal(t, "https://hub.example", svc.HubOrigin())
// A known service is not rewritten, and "" is not a section.
assert.Equal(t, Origin("bench.sr.ht"), Config("bench.sr.ht")["bench.sr.ht"]["origin"])
_, hasEmpty := Config("")[""]
assert.False(t, hasEmpty)
}
// TestOriginsAgreeWithTheConfig pins the one spelling of the fake origins: what
// Origin answers is what a test asserting a rendered link can compare against.
func TestOriginsAgreeWithTheConfig(t *testing.T) {
conf := Config("")
for section, values := range conf {
if !strings.HasSuffix(section, ".sr.ht") {
continue
}
assert.Equal(t, Origin(section), values["origin"], "origin of %s", section)
}
assert.Equal(t, "https://git.example", Origin("git.sr.ht"))
// The section that is configured without an origin, and the non-services.
assert.Empty(t, Origin(NoOrigin))
assert.Empty(t, conf[NoOrigin]["origin"])
assert.Empty(t, Origin("sr.ht"))
assert.Empty(t, Origin("webhooks"))
}
func TestOverridesApply(t *testing.T) {
t.Run("set", func(t *testing.T) {
conf := Config("bench.sr.ht", Set("sr.ht", "environment", "staging"))
assert.Equal(t, "staging", conf["sr.ht"]["environment"])
// The rest of the section survives an override of one key.
assert.Equal(t, SiteName, conf["sr.ht"]["site-name"])
page := chrome.NewService(conf, "bench.sr.ht").
Page(httptest.NewRequest("GET", "/", nil), "t", "")
assert.True(t, page.ShowBanner)
})
t.Run("set creates a missing section", func(t *testing.T) {
conf := Config("", Set("bench.sr.ht", "connection-string", "postgres://x"))
assert.Equal(t, "postgres://x", conf["bench.sr.ht"]["connection-string"])
assert.Equal(t, Origin("bench.sr.ht"), conf["bench.sr.ht"]["origin"])
})
t.Run("delete", func(t *testing.T) {
conf := Config("bench.sr.ht", Delete("hub.sr.ht", "meta.sr.ht"))
assert.NotContains(t, conf, "hub.sr.ht")
assert.NotContains(t, conf, "meta.sr.ht")
assert.Empty(t, chrome.NewService(conf, "bench.sr.ht").HubOrigin())
})
t.Run("section replaces wholesale", func(t *testing.T) {
values := map[string]string{"origin": "https://elsewhere.example"}
conf := Config("bench.sr.ht", Section("git.sr.ht", values))
assert.Equal(t, ini.Section{"origin": "https://elsewhere.example"}, conf["git.sr.ht"])
// The caller's map is copied, not aliased: editing either afterwards
// leaves the other alone.
values["origin"] = "https://mutated.example"
assert.Equal(t, "https://elsewhere.example", conf["git.sr.ht"]["origin"])
})
t.Run("applied in order, after the base config", func(t *testing.T) {
conf := Config("bench.sr.ht",
Set("sr.ht", "site-name", "first"),
Set("sr.ht", "site-name", "second"))
assert.Equal(t, "second", conf["sr.ht"]["site-name"])
})
}
// TestCallsShareNoMutableState is the whole reason Config is a function rather
// than a package-level fixture: the nav tests delete sections and the banner
// tests overwrite keys, and in a shared map the next test reads the wreckage.
func TestCallsShareNoMutableState(t *testing.T) {
first := Config("bench.sr.ht")
delete(first, "hub.sr.ht")
first["sr.ht"]["environment"] = "staging"
first["git.sr.ht"]["origin"] = "https://mutated.example"
second := Config("bench.sr.ht")
assert.Equal(t, "https://hub.example", second["hub.sr.ht"]["origin"])
assert.Equal(t, Environment, second["sr.ht"]["environment"])
assert.Equal(t, "https://git.example", second["git.sr.ht"]["origin"])
// Not merely equal by value: the section maps are distinct allocations, so
// a mutation of one is invisible to the other in either direction.
second["sr.ht"]["site-name"] = "late edit"
assert.Equal(t, SiteName, first["sr.ht"]["site-name"])
}
// TestInitCryptoSealsAndOpens: the bootstrap works, end to end, offline — a
// payload sealed with the installed fernet key opens again, and a signature
// made with the installed webhook key verifies.
func TestInitCryptoSealsAndOpens(t *testing.T) {
InitCrypto()
sealed := crypto.Encrypt([]byte("~alice"))
assert.Equal(t, []byte("~alice"), crypto.DecryptWithoutExpiration(sealed))
payload := []byte(`{"id":1}`)
assert.True(t, crypto.Verify(payload, crypto.Sign(payload)))
nonce, signature := crypto.SignWebhook(payload)
assert.True(t, crypto.VerifyWebhook(payload, nonce, signature))
// Idempotent: a second call — another package's TestMain, in a real service
// — must not rotate the keys the first one sealed with.
InitCrypto()
crypto.InitCrypto(Config("bench.sr.ht"))
assert.Equal(t, []byte("~alice"), crypto.DecryptWithoutExpiration(sealed))
}
// TestConfigCarriesTheKeysCryptoWants guards the two keys by name: crypto reads
// them from a config file and log.Fatals when either is missing, which takes
// the whole test binary with it rather than failing one test.
func TestConfigCarriesTheKeysCryptoWants(t *testing.T) {
conf := Config("bench.sr.ht")
networkKey, ok := conf.Get("sr.ht", "network-key")
require.True(t, ok)
assert.Equal(t, NetworkKey, networkKey)
webhookKey, ok := conf.Get("webhooks", "private-key")
require.True(t, ok)
assert.Equal(t, WebhookKey, webhookKey)
}