package messages import ( "encoding/json" "testing" ) func TestAction_Valid(t *testing.T) { tests := []struct { action Action valid bool }{ {ActionSwitch, true}, {ActionBoot, true}, {ActionTest, true}, {ActionDryActivate, true}, {Action("invalid"), false}, {Action(""), false}, } for _, tc := range tests { t.Run(string(tc.action), func(t *testing.T) { if got := tc.action.Valid(); got != tc.valid { t.Errorf("Action(%q).Valid() = %v, want %v", tc.action, got, tc.valid) } }) } } func TestStatus_IsFinal(t *testing.T) { tests := []struct { status Status final bool }{ {StatusAccepted, false}, {StatusStarted, false}, {StatusCompleted, true}, {StatusFailed, true}, {StatusRejected, true}, } for _, tc := range tests { t.Run(string(tc.status), func(t *testing.T) { if got := tc.status.IsFinal(); got != tc.final { t.Errorf("Status(%q).IsFinal() = %v, want %v", tc.status, got, tc.final) } }) } } func TestDeployRequest_Validate(t *testing.T) { tests := []struct { name string req DeployRequest wantErr bool }{ { name: "valid request with branch", req: DeployRequest{ Action: ActionSwitch, Revision: "master", ReplyTo: "deploy.responses.abc123", }, wantErr: false, }, { name: "valid request with commit hash", req: DeployRequest{ Action: ActionBoot, Revision: "abc123def456", ReplyTo: "deploy.responses.xyz", }, wantErr: false, }, { name: "valid request with feature branch", req: DeployRequest{ Action: ActionTest, Revision: "feature/my-feature", ReplyTo: "deploy.responses.test", }, wantErr: false, }, { name: "valid request with dotted branch", req: DeployRequest{ Action: ActionDryActivate, Revision: "release-1.0.0", ReplyTo: "deploy.responses.test", }, wantErr: false, }, { name: "invalid action", req: DeployRequest{ Action: Action("invalid"), Revision: "master", ReplyTo: "deploy.responses.abc", }, wantErr: true, }, { name: "empty revision", req: DeployRequest{ Action: ActionSwitch, Revision: "", ReplyTo: "deploy.responses.abc", }, wantErr: true, }, { name: "invalid revision with spaces", req: DeployRequest{ Action: ActionSwitch, Revision: "my branch", ReplyTo: "deploy.responses.abc", }, wantErr: true, }, { name: "invalid revision with special chars", req: DeployRequest{ Action: ActionSwitch, Revision: "branch;rm -rf /", ReplyTo: "deploy.responses.abc", }, wantErr: true, }, { name: "empty reply_to", req: DeployRequest{ Action: ActionSwitch, Revision: "master", ReplyTo: "", }, wantErr: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { err := tc.req.Validate() if (err != nil) != tc.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tc.wantErr) } }) } } func TestDeployRequest_Marshal_Unmarshal(t *testing.T) { req := &DeployRequest{ Action: ActionSwitch, Revision: "master", ReplyTo: "deploy.responses.abc123", } data, err := req.Marshal() if err != nil { t.Fatalf("Marshal() error = %v", err) } got, err := UnmarshalDeployRequest(data) if err != nil { t.Fatalf("UnmarshalDeployRequest() error = %v", err) } if got.Action != req.Action || got.Revision != req.Revision || got.ReplyTo != req.ReplyTo { t.Errorf("roundtrip failed: got %+v, want %+v", got, req) } } func TestDeployResponse_Marshal_Unmarshal(t *testing.T) { errCode := ErrorBuildFailed resp := &DeployResponse{ Hostname: "host1", Status: StatusFailed, Error: &errCode, Message: "build failed with exit code 1", } data, err := resp.Marshal() if err != nil { t.Fatalf("Marshal() error = %v", err) } got, err := UnmarshalDeployResponse(data) if err != nil { t.Fatalf("UnmarshalDeployResponse() error = %v", err) } if got.Hostname != resp.Hostname || got.Status != resp.Status || got.Message != resp.Message { t.Errorf("roundtrip failed: got %+v, want %+v", got, resp) } if got.Error == nil || *got.Error != *resp.Error { t.Errorf("error code mismatch: got %v, want %v", got.Error, resp.Error) } } func TestDeployResponse_NullError(t *testing.T) { resp := NewDeployResponse("host1", StatusCompleted, "success") data, err := resp.Marshal() if err != nil { t.Fatalf("Marshal() error = %v", err) } // Verify null error is serialized correctly var m map[string]any if err := json.Unmarshal(data, &m); err != nil { t.Fatalf("json.Unmarshal() error = %v", err) } if m["error"] != nil { t.Errorf("expected null error, got %v", m["error"]) } } func TestDiscoveryRequest_Validate(t *testing.T) { tests := []struct { name string req DiscoveryRequest wantErr bool }{ { name: "valid request", req: DiscoveryRequest{ReplyTo: "deploy.responses.discover-abc"}, wantErr: false, }, { name: "empty reply_to", req: DiscoveryRequest{ReplyTo: ""}, wantErr: true, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { err := tc.req.Validate() if (err != nil) != tc.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tc.wantErr) } }) } } func TestDiscoveryResponse_Marshal_Unmarshal(t *testing.T) { resp := &DiscoveryResponse{ Hostname: "ns1", Tier: "prod", Role: "dns", DeploySubjects: []string{"deploy.prod.ns1", "deploy.prod.all", "deploy.prod.role.dns"}, } data, err := resp.Marshal() if err != nil { t.Fatalf("Marshal() error = %v", err) } got, err := UnmarshalDiscoveryResponse(data) if err != nil { t.Fatalf("UnmarshalDiscoveryResponse() error = %v", err) } if got.Hostname != resp.Hostname || got.Tier != resp.Tier || got.Role != resp.Role { t.Errorf("roundtrip failed: got %+v, want %+v", got, resp) } if len(got.DeploySubjects) != len(resp.DeploySubjects) { t.Errorf("subjects length mismatch: got %d, want %d", len(got.DeploySubjects), len(resp.DeploySubjects)) } } func TestDiscoveryResponse_OmitEmptyRole(t *testing.T) { resp := &DiscoveryResponse{ Hostname: "host1", Tier: "test", Role: "", DeploySubjects: []string{"deploy.test.host1"}, } data, err := resp.Marshal() if err != nil { t.Fatalf("Marshal() error = %v", err) } var m map[string]any if err := json.Unmarshal(data, &m); err != nil { t.Fatalf("json.Unmarshal() error = %v", err) } if _, exists := m["role"]; exists { t.Error("expected role to be omitted when empty") } }