package agentv1 // contract_test.go — agent↔控制面契约快照(支柱 2:契约单源)。 // // 冻结 UsageEntry 的字段面(agent 上报用量的最小单元)。改字段会让本测试失败,逼同步 // proto(server/proto/agent/v1/agent.proto)+ agent 上报端 + 控制面解析端 + 文档。 // dp_uuid 是数据面凭证、绝不等于 user id(控制面侧 dp_uuid→(user_id,device_id) 解析)。 import ( "reflect" "strings" "testing" ) func jsonTagSet(v any) map[string]struct{} { t := reflect.TypeOf(v) if t.Kind() == reflect.Ptr { t = t.Elem() } set := map[string]struct{}{} for i := 0; i < t.NumField(); i++ { tag := t.Field(i).Tag.Get("json") if tag == "" || tag == "-" { continue } name := strings.SplitN(tag, ",", 2)[0] if name != "" { set[name] = struct{}{} } } return set } func assertFrozenKeys(t *testing.T, name string, got map[string]struct{}, want ...string) { t.Helper() wantSet := make(map[string]struct{}, len(want)) for _, k := range want { wantSet[k] = struct{}{} } for k := range got { if _, ok := wantSet[k]; !ok { t.Errorf("%s: 出现未冻结字段 %q —— 改契约需同步 proto + 两端 + 文档", name, k) } } for _, k := range want { if _, ok := got[k]; !ok { t.Errorf("%s: 缺失冻结字段 %q", name, k) } } } func TestContractUsageEntry(t *testing.T) { assertFrozenKeys(t, "UsageEntry", jsonTagSet(UsageEntry{}), "dp_uuid", "bytes_up", "bytes_down", "session_minutes", ) }