Files
member-console/cmd/integration_config_parity_test.go
T
cgalo5758 6dbce6140f Type the ConfigSpec seam and move the connect target to core
Register bool and duration ConfigSpec keys from the Default's type, move
fedwiki's four sync knobs and discourse's two into their integrations'
ConfigSpecs, and replace core's read of fedwiki-custom-domain-target
with a core domains-connect-target key resolved once and threaded
through server and worker config.

Generate init's optional-integration scaffold sections from each
registered ConfigSpec instead of the hand-maintained list, and reword
the Temporal boot warning generically.

Archives the integration-config-parity change; status bookkeeping and
the verify-skill doc follow with the test-stack commit.
2026-08-01 04:13:48 -05:00

132 lines
5.0 KiB
Go

package cmd
import (
"bytes"
"fmt"
"strings"
"testing"
"time"
"git.coopcloud.tech/wiki-cafe/member-console/internal/config"
"git.coopcloud.tech/wiki-cafe/member-console/internal/embeds"
"git.coopcloud.tech/wiki-cafe/member-console/internal/integrations"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// TestRegisterIntegrationConfigFlagsTypes covers the typed ConfigSpec seam
// (integration-config-parity D2): bool and duration keys declared by an
// integration register as flags of the matching type with their declared
// defaults, so no integration knob needs hand-declaring in core.
func TestRegisterIntegrationConfigFlagsTypes(t *testing.T) {
cmd := &cobra.Command{Use: "probe"}
registerIntegrationConfigFlags(cmd)
wantTypes := map[string]string{
"fedwiki-sync-enabled": "bool",
"fedwiki-sync-interval": "duration",
"fedwiki-sync-trigger-immediately": "bool",
"fedwiki-swap-cooldown": "duration",
"discourse-sync-interval": "duration",
"discourse-sync-trigger-immediately": "bool",
"fedwiki-farm-api-url": "string",
"fedwiki-allowed-domains": "stringSlice",
}
for name, wantType := range wantTypes {
f := cmd.Flags().Lookup(name)
if f == nil {
t.Errorf("flag %q not registered", name)
continue
}
if got := f.Value.Type(); got != wantType {
t.Errorf("flag %q type = %q, want %q", name, got, wantType)
}
}
// Declared defaults flow through viper (SetDefault runs in registration).
if got := viper.GetDuration("fedwiki-sync-interval"); got != time.Hour {
t.Errorf("fedwiki-sync-interval default = %v, want 1h", got)
}
if got := viper.GetBool("fedwiki-sync-trigger-immediately"); got != true {
t.Errorf("fedwiki-sync-trigger-immediately default = %v, want true", got)
}
if got := viper.GetDuration("fedwiki-swap-cooldown"); got != 30*24*time.Hour {
t.Errorf("fedwiki-swap-cooldown default = %v, want 720h", got)
}
if got := viper.GetDuration("discourse-sync-interval"); got != 15*time.Minute {
t.Errorf("discourse-sync-interval default = %v, want 15m", got)
}
if got := viper.GetBool("discourse-sync-trigger-immediately"); got != false {
t.Errorf("discourse-sync-trigger-immediately default = %v, want false", got)
}
// The connect target is a core key, not any integration's: no integration
// declares it, and the old integration-namespaced name is gone.
if f := cmd.Flags().Lookup("fedwiki-custom-domain-target"); f != nil {
t.Error("fedwiki-custom-domain-target still registers via an integration ConfigSpec; the key was replaced by core domains-connect-target")
}
}
// TestInitScaffoldGeneratedSections covers the console-init delta: the
// scaffold is the embedded core template plus one generated section per
// declaring integration, stays valid YAML, and hand-lists no integration key.
func TestInitScaffoldGeneratedSections(t *testing.T) {
template, err := embeds.Config.ReadFile("mc-config.yaml")
if err != nil {
t.Fatalf("read embedded template: %v", err)
}
sections := integrationConfigSections()
scaffold := string(template) + sections
// Every registered declaring integration appears with every declared key.
declaring := 0
for _, integ := range integrations.All() {
cp, ok := integ.(config.ConfigProvider)
if !ok {
continue
}
declaring++
displayName := integ.Provider().ProviderManifest().DisplayName
if !strings.Contains(sections, fmt.Sprintf("Optional: %s integration", displayName)) {
t.Errorf("generated sections missing header for %s", displayName)
}
for _, key := range cp.ConfigSpec() {
if !strings.Contains(sections, "# "+key.Name+":") {
t.Errorf("generated sections missing key %q", key.Name)
}
if key.Secret && !strings.Contains(sections, "# "+key.Name+"-file:") {
t.Errorf("generated sections missing secret file variant for %q", key.Name)
}
}
}
if declaring == 0 {
t.Fatal("no declaring integrations registered; test is vacuous")
}
// The embedded core template hand-lists no integration key (generation
// owns them), while required core keys stay.
for _, banned := range []string{"\nstripe-api-key:", "\nfedwiki-farm-api-url:", "\ndiscourse-base-url:"} {
if strings.Contains(string(template), banned) {
t.Errorf("embedded template still hand-lists integration key %q", strings.TrimSpace(banned))
}
}
for _, required := range []string{"base-url:", "db-dsn:", "valkey-addr:", "oidc-idp-issuer-url:", "oidc-sp-client-id:", "csrf-secret:"} {
if !strings.Contains(scaffold, required) {
t.Errorf("scaffold missing required core key %q", required)
}
}
if strings.Contains(scaffold, "realms/master") {
t.Error("scaffold references an administration realm (realms/master)")
}
// The whole scaffold parses as YAML (generated lines are comments).
v := viper.New()
v.SetConfigType("yaml")
if err := v.ReadConfig(bytes.NewReader([]byte(scaffold))); err != nil {
t.Fatalf("scaffold does not parse as YAML: %v", err)
}
if got := v.GetString("base-url"); got == "" {
t.Error("parsed scaffold lost base-url")
}
}