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