- Extract PublicSettingsInjectionPayload named struct with drift test - Add channel_monitor_default_interval_seconds to SSR injection - Add image_output_price to SupportedModelChip - Simplify AppSidebar buildSelfNavItems (admins see available channels) - Add gateway WARN logs for 503 no-available-accounts branches - Wire ChannelMonitorRunner into provideCleanup for graceful shutdown - Add migrations 130/131 (CC template userid fix + mimicry field cleanup) - Clean up fork-only features (sora, claude max simulation, client affinity) - Remove ~320 obsolete i18n keys - Add codexUsage utility, WechatServiceButton, BulkEditAccountModal - Tidy go.sum
69 lines
2.3 KiB
Go
69 lines
2.3 KiB
Go
package dto
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
)
|
|
|
|
// TestPublicSettingsInjectionPayload_SchemaDoesNotDrift guarantees the SSR
|
|
// injection struct exposes every JSON field consumed by the frontend.
|
|
//
|
|
// Why this test exists: before we extracted a named PublicSettingsInjectionPayload
|
|
// type, the inline struct was manually kept in sync with dto.PublicSettings and
|
|
// drifted — ChannelMonitorEnabled / AvailableChannelsEnabled were missing, which
|
|
// made the frontend read `undefined` on refresh and hide the "可用渠道" menu
|
|
// until the async /api/v1/settings/public round-trip finished.
|
|
//
|
|
// This test compares the two JSON-tag sets and fails if injection is missing
|
|
// any field that dto.PublicSettings exposes. Adding a new feature flag with
|
|
// only a DTO entry will fail this test until the injection struct is updated.
|
|
//
|
|
// Intentional exclusions (fields present on dto.PublicSettings that SSR does
|
|
// not need to inject) are listed in `dtoOnlyFields` below with a reason.
|
|
func TestPublicSettingsInjectionPayload_SchemaDoesNotDrift(t *testing.T) {
|
|
injection := jsonTags(reflect.TypeOf(service.PublicSettingsInjectionPayload{}))
|
|
dtoKeys := jsonTags(reflect.TypeOf(PublicSettings{}))
|
|
|
|
// Fields that legitimately live only on the DTO. Keep tiny; document each.
|
|
dtoOnlyFields := map[string]string{
|
|
// sora_client_enabled is an upstream-only field the fork does not surface.
|
|
"sora_client_enabled": "upstream-only field, not used on this fork",
|
|
}
|
|
|
|
var missing []string
|
|
for key := range dtoKeys {
|
|
if _, ok := injection[key]; ok {
|
|
continue
|
|
}
|
|
if _, allowed := dtoOnlyFields[key]; allowed {
|
|
continue
|
|
}
|
|
missing = append(missing, key)
|
|
}
|
|
if len(missing) > 0 {
|
|
t.Fatalf("service.PublicSettingsInjectionPayload is missing JSON fields present on dto.PublicSettings: %s\n"+
|
|
"add the field to PublicSettingsInjectionPayload (and GetPublicSettingsForInjection), or "+
|
|
"document the exclusion in dtoOnlyFields with a reason.", strings.Join(missing, ", "))
|
|
}
|
|
}
|
|
|
|
func jsonTags(t reflect.Type) map[string]struct{} {
|
|
out := make(map[string]struct{})
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
tag := f.Tag.Get("json")
|
|
if tag == "" || tag == "-" {
|
|
continue
|
|
}
|
|
name := strings.SplitN(tag, ",", 2)[0]
|
|
if name == "" {
|
|
continue
|
|
}
|
|
out[name] = struct{}{}
|
|
}
|
|
return out
|
|
}
|