@@ -728,3 +728,167 @@ func TestPostOneRespectsRetryAfterAcrossGoroutines(t *testing.T) {
728728 }
729729 wg .Wait ()
730730}
731+
732+ func TestPostCluster (t * testing.T ) {
733+ tests := []struct {
734+ name string
735+ records []* DeploymentRecord
736+ handler http.HandlerFunc
737+ wantErr bool
738+ errType any
739+ errContain string
740+ wantBody bool
741+ wantOk float64
742+ wantUnknownArtifact float64
743+ wantSoftFail float64
744+ wantHardFail float64
745+ wantClientError float64
746+ }{
747+ {
748+ name : "empty records returns nil" ,
749+ records : []* DeploymentRecord {},
750+ handler : func (_ http.ResponseWriter , _ * http.Request ) {
751+ t .Fatal ("server should not be called with empty records" )
752+ },
753+ },
754+ {
755+ name : "success on 207 returns body" ,
756+ records : []* DeploymentRecord {testRecord ()},
757+ handler : func (w http.ResponseWriter , _ * http.Request ) {
758+ w .WriteHeader (http .StatusMultiStatus )
759+ _ , _ = w .Write ([]byte (`{"total_count":1,"deployment_records":[],"errors":[]}` ))
760+ },
761+ wantBody : true ,
762+ wantOk : 1 ,
763+ },
764+ {
765+ name : "404 returns ClusterNoRepositoriesError" ,
766+ records : []* DeploymentRecord {testRecord ()},
767+ handler : func (w http.ResponseWriter , _ * http.Request ) {
768+ w .WriteHeader (http .StatusNotFound )
769+ },
770+ wantErr : true ,
771+ errType : & ClusterNoRepositoriesError {},
772+ wantUnknownArtifact : 1 ,
773+ },
774+ {
775+ name : "400 returns client error" ,
776+ records : []* DeploymentRecord {testRecord ()},
777+ handler : func (w http.ResponseWriter , _ * http.Request ) {
778+ w .WriteHeader (http .StatusBadRequest )
779+ _ , _ = w .Write ([]byte ("bad request" ))
780+ },
781+ wantErr : true ,
782+ errContain : "client error" ,
783+ wantClientError : 1 ,
784+ },
785+ {
786+ name : "500 retries exhausted returns error" ,
787+ records : []* DeploymentRecord {testRecord ()},
788+ handler : func (w http.ResponseWriter , _ * http.Request ) {
789+ w .WriteHeader (http .StatusInternalServerError )
790+ },
791+ wantErr : true ,
792+ errContain : "all retries exhausted" ,
793+ wantSoftFail : 1 ,
794+ wantHardFail : 1 ,
795+ },
796+ }
797+
798+ for _ , tt := range tests {
799+ t .Run (tt .name , func (t * testing.T ) {
800+ srv := httptest .NewServer (tt .handler )
801+ t .Cleanup (srv .Close )
802+
803+ client , err := NewClient (srv .URL , "test-org" , WithRetries (0 ))
804+ if err != nil {
805+ t .Fatalf ("failed to create client: %v" , err )
806+ }
807+
808+ counters := allCounters ()
809+ snapshots := make ([]float64 , len (counters ))
810+ for i , c := range counters {
811+ snapshots [i ] = testutil .ToFloat64 (c )
812+ }
813+
814+ ctx , cancel := context .WithTimeout (context .Background (), 10 * time .Second )
815+ t .Cleanup (cancel )
816+
817+ respBody , err := client .PostCluster (ctx , tt .records , "test-cluster" )
818+
819+ if tt .wantErr {
820+ if err == nil {
821+ t .Fatal ("expected error, got nil" )
822+ }
823+ if tt .errType != nil {
824+ switch tt .errType .(type ) {
825+ case * ClusterNoRepositoriesError :
826+ var e * ClusterNoRepositoriesError
827+ if ! errors .As (err , & e ) {
828+ t .Errorf ("expected ClusterNoRepositoriesError, got %T: %v" , err , err )
829+ }
830+ default :
831+ t .Fatalf ("unexpected error type in test: %T" , tt .errType )
832+ }
833+ }
834+ if tt .errContain != "" && ! strings .Contains (err .Error (), tt .errContain ) {
835+ t .Errorf ("error %q should contain %q" , err .Error (), tt .errContain )
836+ }
837+ } else if err != nil {
838+ t .Fatalf ("unexpected error: %v" , err )
839+ }
840+
841+ if tt .wantBody && respBody == nil {
842+ t .Error ("expected non-nil response body" )
843+ }
844+
845+ wantDeltas := []float64 {
846+ tt .wantOk ,
847+ tt .wantUnknownArtifact ,
848+ 0 , // rate limited
849+ tt .wantSoftFail ,
850+ tt .wantHardFail ,
851+ tt .wantClientError ,
852+ }
853+ names := []string {
854+ "PostDeploymentRecordOk" ,
855+ "PostDeploymentRecordUnknownArtifact" ,
856+ "PostDeploymentRecordRateLimited" ,
857+ "PostDeploymentRecordSoftFail" ,
858+ "PostDeploymentRecordHardFail" ,
859+ "PostDeploymentRecordClientError" ,
860+ }
861+ for i , c := range counters {
862+ got := testutil .ToFloat64 (c ) - snapshots [i ]
863+ if got != wantDeltas [i ] {
864+ t .Errorf ("%s delta = %v, want %v" , names [i ], got , wantDeltas [i ])
865+ }
866+ }
867+ })
868+ }
869+ }
870+
871+ func TestPostCluster_URLEscapesCluster (t * testing.T ) {
872+ var rawPath string
873+ srv := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
874+ rawPath = r .URL .RawPath
875+ w .WriteHeader (http .StatusOK )
876+ _ , _ = w .Write ([]byte (`{"total_count":0,"deployment_records":[]}` ))
877+ }))
878+ t .Cleanup (srv .Close )
879+
880+ client , err := NewClient (srv .URL , "test-org" , WithRetries (0 ))
881+ if err != nil {
882+ t .Fatalf ("failed to create client: %v" , err )
883+ }
884+
885+ _ , err = client .PostCluster (context .Background (), []* DeploymentRecord {testRecord ()}, "cluster/with spaces" )
886+ if err != nil {
887+ t .Fatalf ("unexpected error: %v" , err )
888+ }
889+
890+ wantSuffix := "/cluster/cluster%2Fwith%20spaces"
891+ if ! strings .HasSuffix (rawPath , wantSuffix ) {
892+ t .Errorf ("raw path %q should end with %q" , rawPath , wantSuffix )
893+ }
894+ }
0 commit comments