diff --git a/controllers/individual_signing.go b/controllers/individual_signing.go index 29ddfb6..3c373ee 100644 --- a/controllers/individual_signing.go +++ b/controllers/individual_signing.go @@ -141,9 +141,8 @@ func (ctl *IndividualSigningController) Check() { ctl.GetString(":link_id"), ctl.GetString("email"), ) - // 非 debug 模式时隐藏调试信息 + // status 是权威状态字段,始终返回;调试信息仅在 debug 模式下返回 if !debug { - v.Status = "" v.DebugInfo = nil } diff --git a/models/individual_signing.go b/models/individual_signing.go index aa23155..d6ed21e 100644 --- a/models/individual_signing.go +++ b/models/individual_signing.go @@ -29,7 +29,7 @@ type IndividualSigningInfo struct { type IndividualSigned struct { Type string `json:"type"` // "individual" 或 "corp" Signed bool `json:"signed"` // 是否已签署过 - VersionMatched bool `json:"version_matched,omitempty"` // 签署是否当前有效(考虑宽限期) + VersionMatched bool `json:"version_matched"` // 签署是否当前有效(考虑宽限期),始终返回以兼容旧版本调用 Status string `json:"status"` // 内部状态:"not_signed" | "valid" | "expired" DebugInfo *DebugInfo `json:"_debug,omitempty"` // 调试信息(仅debug=true时返回) } diff --git a/models/individual_signing_test.go b/models/individual_signing_test.go new file mode 100644 index 0000000..db763d2 --- /dev/null +++ b/models/individual_signing_test.go @@ -0,0 +1,125 @@ +package models + +import ( + "encoding/json" + "testing" +) + +// TestIndividualSignedJSONSerialization verifies that the Check API response +// always includes signed, version_matched and status fields so that legacy +// callers keep working and the new authoritative status field is always present. +// +// Requirement (#909): the response must look like +// +// {"data":{"type":"corporation","signed":true,"version_matched":true,"status":"valid"}} +// +// for all three states (not_signed / valid / expired), signed and +// version_matched must be present even when false. +func TestIndividualSignedJSONSerialization(t *testing.T) { + tests := []struct { + name string + in IndividualSigned + want map[string]interface{} + }{ + { + name: "valid", + in: IndividualSigned{ + Type: "corporation", + Signed: true, + VersionMatched: true, + Status: "valid", + }, + want: map[string]interface{}{ + "type": "corporation", + "signed": true, + "version_matched": true, + "status": "valid", + }, + }, + { + name: "expired keeps version_matched:false", + in: IndividualSigned{ + Type: "corporation", + Signed: true, + VersionMatched: false, + Status: "expired", + }, + want: map[string]interface{}{ + "type": "corporation", + "signed": true, + "version_matched": false, + "status": "expired", + }, + }, + { + name: "not_signed keeps signed:false and version_matched:false", + in: IndividualSigned{ + Type: "individual", + Signed: false, + VersionMatched: false, + Status: "not_signed", + }, + want: map[string]interface{}{ + "type": "individual", + "signed": false, + "version_matched": false, + "status": "not_signed", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + raw, err := json.Marshal(tt.in) + if err != nil { + t.Fatalf("json.Marshal returned unexpected error: %v", err) + } + + var got map[string]interface{} + if err := json.Unmarshal(raw, &got); err != nil { + t.Fatalf("json.Unmarshal returned unexpected error: %v", err) + } + + for k, wantVal := range tt.want { + gotVal, ok := got[k] + if !ok { + t.Errorf("missing field %q in response: %s", k, string(raw)) + continue + } + if gotVal != wantVal { + t.Errorf("field %q = %v (%T), want %v (%T)", + k, gotVal, gotVal, wantVal, wantVal) + } + } + + if _, ok := got["_debug"]; ok { + t.Errorf("_debug should be omitted when DebugInfo is nil, got: %s", string(raw)) + } + }) + } +} + +// TestIndividualSignedDebugInfoOmittedWhenNil ensures the debug payload is +// only exposed when populated (i.e. debug=true). +func TestIndividualSignedDebugInfoOmittedWhenNil(t *testing.T) { + in := IndividualSigned{ + Type: "individual", + Signed: true, + VersionMatched: true, + Status: "valid", + } + + raw, err := json.Marshal(in) + if err != nil { + t.Fatalf("json.Marshal returned unexpected error: %v", err) + } + + var got map[string]interface{} + if err := json.Unmarshal(raw, &got); err != nil { + t.Fatalf("json.Unmarshal returned unexpected error: %v", err) + } + + if _, ok := got["_debug"]; ok { + t.Errorf("_debug must be omitted when nil, got: %s", string(raw)) + } +}