@@ -593,6 +593,10 @@ func TestResourceHandler_PatchByOwner(t *testing.T) {
593593 {
594594 name : "Success" ,
595595 setupMock : func (mock * services.MockResourceService ) {
596+ mock .EXPECT ().Get (gomock .Any (), "Channel" , "ch-1" ).Return (& api.Resource {
597+ Meta : api.Meta {ID : "ch-1" }, Kind : "Channel" ,
598+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
599+ }, nil )
596600 mock .EXPECT ().GetByOwner (gomock .Any (), "Version" , "v-1" , "ch-1" ).
597601 Return (& api.Resource {Meta : api.Meta {ID : "v-1" }, Kind : "Version" ,
598602 Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" }, nil )
@@ -609,6 +613,10 @@ func TestResourceHandler_PatchByOwner(t *testing.T) {
609613 {
610614 name : "Child not owned by parent" ,
611615 setupMock : func (mock * services.MockResourceService ) {
616+ mock .EXPECT ().Get (gomock .Any (), "Channel" , "ch-1" ).Return (& api.Resource {
617+ Meta : api.Meta {ID : "ch-1" }, Kind : "Channel" ,
618+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
619+ }, nil )
612620 mock .EXPECT ().GetByOwner (gomock .Any (), "Version" , "v-1" , "ch-1" ).
613621 Return (nil , errors .NotFound ("Version not found for channel" ))
614622 },
@@ -617,6 +625,10 @@ func TestResourceHandler_PatchByOwner(t *testing.T) {
617625 {
618626 name : "Conflict 409 - soft-deleted child" ,
619627 setupMock : func (mock * services.MockResourceService ) {
628+ mock .EXPECT ().Get (gomock .Any (), "Channel" , "ch-1" ).Return (& api.Resource {
629+ Meta : api.Meta {ID : "ch-1" }, Kind : "Channel" ,
630+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
631+ }, nil )
620632 mock .EXPECT ().GetByOwner (gomock .Any (), "Version" , "v-1" , "ch-1" ).
621633 Return (& api.Resource {Meta : api.Meta {ID : "v-1" }, Kind : "Version" ,
622634 Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" }, nil )
@@ -626,6 +638,14 @@ func TestResourceHandler_PatchByOwner(t *testing.T) {
626638 },
627639 expectedStatusCode : http .StatusConflict ,
628640 },
641+ {
642+ name : "Parent not found" ,
643+ setupMock : func (mock * services.MockResourceService ) {
644+ mock .EXPECT ().Get (gomock .Any (), "Channel" , "ch-1" ).
645+ Return (nil , errors .NotFound ("Channel not found" ))
646+ },
647+ expectedStatusCode : http .StatusNotFound ,
648+ },
629649 }
630650
631651 for _ , tt := range tests {
@@ -659,6 +679,10 @@ func TestResourceHandler_DeleteByOwner(t *testing.T) {
659679 {
660680 name : "Success" ,
661681 setupMock : func (mock * services.MockResourceService ) {
682+ mock .EXPECT ().Get (gomock .Any (), "Channel" , "ch-1" ).Return (& api.Resource {
683+ Meta : api.Meta {ID : "ch-1" }, Kind : "Channel" ,
684+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
685+ }, nil )
662686 mock .EXPECT ().GetByOwner (gomock .Any (), "Version" , "v-1" , "ch-1" ).
663687 Return (& api.Resource {Meta : api.Meta {ID : "v-1" }, Kind : "Version" ,
664688 Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" }, nil )
@@ -673,11 +697,23 @@ func TestResourceHandler_DeleteByOwner(t *testing.T) {
673697 {
674698 name : "Child not owned by parent" ,
675699 setupMock : func (mock * services.MockResourceService ) {
700+ mock .EXPECT ().Get (gomock .Any (), "Channel" , "ch-1" ).Return (& api.Resource {
701+ Meta : api.Meta {ID : "ch-1" }, Kind : "Channel" ,
702+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
703+ }, nil )
676704 mock .EXPECT ().GetByOwner (gomock .Any (), "Version" , "v-1" , "ch-1" ).
677705 Return (nil , errors .NotFound ("Version not found for channel" ))
678706 },
679707 expectedStatusCode : http .StatusNotFound ,
680708 },
709+ {
710+ name : "Parent not found" ,
711+ setupMock : func (mock * services.MockResourceService ) {
712+ mock .EXPECT ().Get (gomock .Any (), "Channel" , "ch-1" ).
713+ Return (nil , errors .NotFound ("Channel not found" ))
714+ },
715+ expectedStatusCode : http .StatusNotFound ,
716+ },
681717 }
682718
683719 for _ , tt := range tests {
@@ -817,6 +853,10 @@ func TestResourceHandler_ForceDeleteByOwner(t *testing.T) {
817853 name : "Success 204 - nested resource force-deleted" ,
818854 body : `{"reason": "Stuck in finalizing"}` ,
819855 setupMock : func (mock * services.MockResourceService ) {
856+ mock .EXPECT ().Get (gomock .Any (), "Channel" , parentID ).Return (& api.Resource {
857+ Meta : api.Meta {ID : parentID }, Kind : "Channel" ,
858+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
859+ }, nil )
820860 mock .EXPECT ().
821861 GetByOwner (gomock .Any (), "Version" , versionID , parentID ).
822862 Return (& api.Resource {Meta : api.Meta {ID : versionID }, Kind : "Version" }, nil )
@@ -830,12 +870,25 @@ func TestResourceHandler_ForceDeleteByOwner(t *testing.T) {
830870 name : "Error 404 - ownership mismatch" ,
831871 body : `{"reason": "some reason"}` ,
832872 setupMock : func (mock * services.MockResourceService ) {
873+ mock .EXPECT ().Get (gomock .Any (), "Channel" , parentID ).Return (& api.Resource {
874+ Meta : api.Meta {ID : parentID }, Kind : "Channel" ,
875+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
876+ }, nil )
833877 mock .EXPECT ().
834878 GetByOwner (gomock .Any (), "Version" , versionID , parentID ).
835879 Return (nil , errors .NotFound ("Version with id='%s' not found for owner '%s'" , versionID , parentID ))
836880 },
837881 expectedStatusCode : http .StatusNotFound ,
838882 },
883+ {
884+ name : "Error 404 - parent not found" ,
885+ body : `{"reason": "some reason"}` ,
886+ setupMock : func (mock * services.MockResourceService ) {
887+ mock .EXPECT ().Get (gomock .Any (), "Channel" , parentID ).
888+ Return (nil , errors .NotFound ("Channel not found" ))
889+ },
890+ expectedStatusCode : http .StatusNotFound ,
891+ },
839892 {
840893 name : "Error 400 - empty reason" ,
841894 body : `{"reason": ""}` ,
@@ -847,6 +900,10 @@ func TestResourceHandler_ForceDeleteByOwner(t *testing.T) {
847900 name : "Error 409 - not in Finalizing state" ,
848901 body : `{"reason": "some reason"}` ,
849902 setupMock : func (mock * services.MockResourceService ) {
903+ mock .EXPECT ().Get (gomock .Any (), "Channel" , parentID ).Return (& api.Resource {
904+ Meta : api.Meta {ID : parentID }, Kind : "Channel" ,
905+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
906+ }, nil )
850907 mock .EXPECT ().
851908 GetByOwner (gomock .Any (), "Version" , versionID , parentID ).
852909 Return (& api.Resource {Meta : api.Meta {ID : versionID }, Kind : "Version" }, nil )
@@ -860,6 +917,10 @@ func TestResourceHandler_ForceDeleteByOwner(t *testing.T) {
860917 name : "Error 500 - service internal error" ,
861918 body : `{"reason": "some reason"}` ,
862919 setupMock : func (mock * services.MockResourceService ) {
920+ mock .EXPECT ().Get (gomock .Any (), "Channel" , parentID ).Return (& api.Resource {
921+ Meta : api.Meta {ID : parentID }, Kind : "Channel" ,
922+ Spec : datatypes .JSON (`{}` ), CreatedBy : "u@t.com" , UpdatedBy : "u@t.com" ,
923+ }, nil )
863924 mock .EXPECT ().
864925 GetByOwner (gomock .Any (), "Version" , versionID , parentID ).
865926 Return (& api.Resource {Meta : api.Meta {ID : versionID }, Kind : "Version" }, nil )
@@ -1110,3 +1171,29 @@ func TestRootResourceHandler_Create_RejectsInvalidName(t *testing.T) {
11101171 })
11111172 }
11121173}
1174+
1175+ func TestResourceHandler_Create_ChildKindWithoutParent_Returns422 (t * testing.T ) {
1176+ RegisterTestingT (t )
1177+ ctrl := gomock .NewController (t )
1178+ defer ctrl .Finish ()
1179+
1180+ t .Cleanup (registry .Reset )
1181+ registry .Reset ()
1182+ registry .Register (channelDescriptor )
1183+ registry .Register (versionDescriptor )
1184+
1185+ mockSvc := services .NewMockResourceService (ctrl )
1186+ handler := NewResourceHandler (versionDescriptor , mockSvc )
1187+
1188+ req := httptest .NewRequest (http .MethodPost ,
1189+ "/api/hyperfleet/v1/versions" ,
1190+ strings .NewReader (`{"kind":"Version","name":"4-17-3","spec":{"raw_version":"4.17.3"}}` ))
1191+ req .Header .Set ("Content-Type" , "application/json" )
1192+ req = mux .SetURLVars (req , map [string ]string {})
1193+ rr := httptest .NewRecorder ()
1194+
1195+ handler .Create (rr , req )
1196+
1197+ Expect (rr .Code ).To (Equal (http .StatusUnprocessableEntity ))
1198+ Expect (rr .Body .String ()).To (ContainSubstring ("/channels/{id}/versions" ))
1199+ }
0 commit comments