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.
132 lines
5.0 KiB
Go
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")
|
|
}
|
|
}
|