package main import ( "fmt" "io" "net/http" "net/http/httptest" "strings" "testing" "time" ) // TestAssertStatusCode verifies AssertStatusCode correctly compares status codes func TestAssertStatusCode(t *testing.T) { t.Run("matching status code passes", func(t *testing.T) { resp := &http.Response{StatusCode: http.StatusOK} AssertStatusCode(t, resp, http.StatusOK) }) t.Run("various matching status codes", func(t *testing.T) { codes := []struct { actual int expected int }{ {200, 200}, {201, 201}, {404, 404}, {500, 500}, } for _, tc := range codes { t.Run(http.StatusText(tc.actual), func(t *testing.T) { resp := &http.Response{StatusCode: tc.actual} AssertStatusCode(t, resp, tc.expected) }) } }) t.Run("all common status codes match exactly", func(t *testing.T) { commonCodes := []int{ http.StatusOK, // 200 http.StatusCreated, // 201 http.StatusAccepted, // 202 http.StatusNoContent, // 204 http.StatusMovedPermanently, // 301 http.StatusFound, // 302 http.StatusBadRequest, // 400 http.StatusUnauthorized, // 401 http.StatusForbidden, // 403 http.StatusNotFound, // 404 http.StatusTooManyRequests, // 429 http.StatusInternalServerError, // 500 http.StatusBadGateway, // 502 http.StatusServiceUnavailable, // 503 } for _, code := range commonCodes { t.Run(fmt.Sprintf("%d", code), func(t *testing.T) { resp := &http.Response{ StatusCode: code, Body: io.NopCloser(strings.NewReader("")), } defer resp.Body.Close() // Should pass without error when codes match AssertStatusCode(t, resp, code) }) } }) } // TestAssertJSONBody verifies AssertJSONBody validates JSON and returns boolean func TestAssertJSONBody(t *testing.T) { t.Run("valid JSON object returns true", func(t *testing.T) { body := `{"id":"msg123","content":"hello"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := AssertJSONBody(t, httpResp) if result != true { t.Errorf("Expected true for valid JSON, got false") } }) t.Run("valid JSON array returns true", func(t *testing.T) { body := `[{"id":"msg123"},{"id":"msg456"}]` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := AssertJSONBody(t, httpResp) if result != true { t.Errorf("Expected true for valid JSON array, got false") } }) t.Run("complex nested JSON returns true", func(t *testing.T) { body := `{"id":"msg123","content":{"type":"text","text":"hello"},"usage":{"input":10,"output":5}}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := AssertJSONBody(t, httpResp) if result != true { t.Errorf("Expected true for complex nested JSON, got false") } }) t.Run("empty body would fail assertion", func(t *testing.T) { // We can't test the actual failure without triggering a test error // But we verify the function behavior by inspection body := "" resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } // AssertJSONBody(t, httpResp) // Would fail - empty body is not valid JSON _ = httpResp // Verify we can create responses }) t.Run("invalid JSON would fail assertion", func(t *testing.T) { // We can't test the actual failure without triggering a test error // But we verify the function behavior by inspection body := `{invalid json` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } // AssertJSONBody(t, httpResp) // Would fail - invalid JSON _ = httpResp // Verify we can create responses }) } // TestAssertResponseField verifies AssertResponseField validates specific JSON field values func TestAssertResponseField(t *testing.T) { t.Run("existing string field with matching value", func(t *testing.T) { body := `{"id":"msg123","content":"hello"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } AssertResponseField(t, httpResp, "id", "msg123") }) t.Run("multiple field checks", func(t *testing.T) { body := `{"id":"msg123","content":"hello","type":"message"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } AssertResponseField(t, httpResp, "id", "msg123") }) t.Run("numeric field values", func(t *testing.T) { body := `{"count":42,"price":19.99}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } // Note: JSON numbers unmarshal to float64 AssertResponseField(t, httpResp, "count", float64(42)) }) t.Run("boolean field values", func(t *testing.T) { body := `{"active":true,"deleted":false}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } AssertResponseField(t, httpResp, "active", true) }) } // TestAssertEmptyBody verifies AssertEmptyBody checks for zero-length response func TestAssertEmptyBody(t *testing.T) { t.Run("empty body passes", func(t *testing.T) { body := "" resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } AssertEmptyBody(t, httpResp) }) t.Run("non-empty body fails assertion", func(t *testing.T) { // We can't test the actual failure without triggering a test error // But we verify the function exists and behavior by inspection body := "some content" resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } // AssertEmptyBody(t, httpResp) // Would fail _ = httpResp // Verify we can create responses }) } // TestAssertHeader verifies AssertHeader validates header values func TestAssertHeader(t *testing.T) { t.Run("matching header value passes", func(t *testing.T) { resp := &http.Response{ Header: http.Header{"Content-Type": []string{"application/json"}}, } AssertHeader(t, resp, "Content-Type", "application/json") }) t.Run("case-insensitive header name", func(t *testing.T) { resp := &http.Response{ Header: http.Header{"Content-Type": []string{"application/json"}}, } AssertHeader(t, resp, "content-type", "application/json") }) t.Run("multiple headers", func(t *testing.T) { resp := &http.Response{ Header: http.Header{ "Content-Type": []string{"application/json"}, "X-Custom-Auth": []string{"Bearer token123"}, "Cache-Control": []string{"no-cache"}, }, } AssertHeader(t, resp, "Content-Type", "application/json") AssertHeader(t, resp, "X-Custom-Auth", "Bearer token123") AssertHeader(t, resp, "Cache-Control", "no-cache") }) t.Run("multiple header values (first one is used)", func(t *testing.T) { resp := &http.Response{ Header: http.Header{"X-Custom": []string{"value1", "value2"}}, } AssertHeader(t, resp, "X-Custom", "value1") }) } // TestAssertContentType verifies AssertContentType validates Content-Type specifically func TestAssertContentType(t *testing.T) { t.Run("JSON Content-Type", func(t *testing.T) { resp := &http.Response{ Header: http.Header{"Content-Type": []string{"application/json"}}, } AssertContentType(t, resp, "application/json") }) t.Run("Content-Type with charset", func(t *testing.T) { resp := &http.Response{ Header: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}}, } AssertContentType(t, resp, "application/json; charset=utf-8") }) t.Run("text/event-stream Content-Type", func(t *testing.T) { resp := &http.Response{ Header: http.Header{"Content-Type": []string{"text/event-stream"}}, } AssertContentType(t, resp, "text/event-stream") }) t.Run("text/html Content-Type", func(t *testing.T) { resp := &http.Response{ Header: http.Header{"Content-Type": []string{"text/html"}}, } AssertContentType(t, resp, "text/html") }) } // TestReadResponseBody verifies ReadResponseBody reads response body correctly func TestReadResponseBody(t *testing.T) { t.Run("reads non-empty body", func(t *testing.T) { body := "hello world" resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadResponseBody(t, httpResp) if string(result) != body { t.Errorf("Expected %q, got %q", body, string(result)) } }) t.Run("reads empty body", func(t *testing.T) { body := "" resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadResponseBody(t, httpResp) if len(result) != 0 { t.Errorf("Expected empty slice, got %d bytes", len(result)) } }) t.Run("reads binary data", func(t *testing.T) { body := "\x00\x01\x02\x03\xff\xfe" resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadResponseBody(t, httpResp) if string(result) != body { t.Errorf("Binary data mismatch") } }) t.Run("reads large body", func(t *testing.T) { body := strings.Repeat("x", 10000) resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadResponseBody(t, httpResp) if len(result) != 10000 { t.Errorf("Expected 10000 bytes, got %d", len(result)) } }) t.Run("reads JSON body", func(t *testing.T) { body := `{"id":"msg123","content":"hello"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadResponseBody(t, httpResp) if string(result) != body { t.Errorf("Expected %q, got %q", body, string(result)) } }) } // TestReadJSONResponse verifies ReadJSONResponse parses JSON into map correctly func TestReadJSONResponse(t *testing.T) { t.Run("parses simple JSON object", func(t *testing.T) { body := `{"id":"msg123","content":"hello"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadJSONResponse(t, httpResp) if result["id"] != "msg123" { t.Errorf("Expected id=msg123, got %v", result["id"]) } if result["content"] != "hello" { t.Errorf("Expected content=hello, got %v", result["content"]) } }) t.Run("parses nested JSON object", func(t *testing.T) { body := `{"user":{"name":"Alice","age":30}}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadJSONResponse(t, httpResp) user, ok := result["user"].(map[string]interface{}) if !ok { t.Fatal("user field is not a map") } if user["name"] != "Alice" { t.Errorf("Expected name=Alice, got %v", user["name"]) } }) t.Run("parses JSON with various types", func(t *testing.T) { body := `{"string":"text","number":42,"float":3.14,"bool":true,"null":null}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadJSONResponse(t, httpResp) if result["string"] != "text" { t.Errorf("Expected string=text, got %v", result["string"]) } // Note: JSON numbers unmarshal to float64 if num, ok := result["number"].(float64); !ok || num != 42.0 { t.Errorf("Expected number=42.0, got %v", result["number"]) } if result["bool"] != true { t.Errorf("Expected bool=true, got %v", result["bool"]) } if result["null"] != nil { t.Errorf("Expected null=nil, got %v", result["null"]) } }) t.Run("parses complex API response", func(t *testing.T) { body := `{"id":"msg_test123","type":"message","role":"assistant","content":[{"type":"text","text":"Hello!"}],"usage":{"input_tokens":10,"output_tokens":5}}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } result := ReadJSONResponse(t, httpResp) if result["id"] != "msg_test123" { t.Errorf("Expected id=msg_test123, got %v", result["id"]) } if result["type"] != "message" { t.Errorf("Expected type=message, got %v", result["type"]) } }) } // TestAssertDurationAtLeast verifies AssertDurationAtLeast validates minimum durations func TestAssertDurationAtLeast(t *testing.T) { t.Run("duration equal to minimum passes", func(t *testing.T) { actual := 100 * time.Millisecond minimum := 100 * time.Millisecond AssertDurationAtLeast(t, actual, minimum, "duration check") }) t.Run("duration greater than minimum passes", func(t *testing.T) { actual := 150 * time.Millisecond minimum := 100 * time.Millisecond AssertDurationAtLeast(t, actual, minimum, "duration check") }) t.Run("zero actual duration vs zero minimum", func(t *testing.T) { actual := 0 * time.Millisecond minimum := 0 * time.Millisecond AssertDurationAtLeast(t, actual, minimum, "zero check") }) t.Run("large duration values", func(t *testing.T) { actual := 3600 * time.Second minimum := 1800 * time.Second AssertDurationAtLeast(t, actual, minimum, "large duration") }) t.Run("microsecond precision", func(t *testing.T) { actual := 1500 * time.Microsecond minimum := 1000 * time.Microsecond AssertDurationAtLeast(t, actual, minimum, "microsecond check") }) t.Run("nanosecond precision", func(t *testing.T) { actual := 1500 * time.Nanosecond minimum := 1000 * time.Nanosecond AssertDurationAtLeast(t, actual, minimum, "nanosecond check") }) } // TestAssertDurationInRange verifies AssertDurationInRange validates duration ranges func TestAssertDurationInRange(t *testing.T) { t.Run("duration within range passes", func(t *testing.T) { actual := 150 * time.Millisecond min := 100 * time.Millisecond max := 200 * time.Millisecond AssertDurationInRange(t, actual, min, max, "range check") }) t.Run("duration at minimum boundary passes", func(t *testing.T) { actual := 100 * time.Millisecond min := 100 * time.Millisecond max := 200 * time.Millisecond AssertDurationInRange(t, actual, min, max, "boundary check min") }) t.Run("duration at maximum boundary passes", func(t *testing.T) { actual := 200 * time.Millisecond min := 100 * time.Millisecond max := 200 * time.Millisecond AssertDurationInRange(t, actual, min, max, "boundary check max") }) t.Run("equal min and max (exact match)", func(t *testing.T) { actual := 100 * time.Millisecond min := 100 * time.Millisecond max := 100 * time.Millisecond AssertDurationInRange(t, actual, min, max, "exact match") }) t.Run("large duration range", func(t *testing.T) { actual := 30 * time.Minute min := 15 * time.Minute max := 60 * time.Minute AssertDurationInRange(t, actual, min, max, "large range") }) t.Run("small duration range", func(t *testing.T) { actual := 150 * time.Microsecond min := 100 * time.Microsecond max := 200 * time.Microsecond AssertDurationInRange(t, actual, min, max, "small range") }) } // TestCombinedAssertions verifies assertions work together in realistic scenarios func TestCombinedAssertions(t *testing.T) { t.Run("typical API response validation", func(t *testing.T) { // Simulate a typical API response body := `{"id":"msg123","type":"message","content":"hello world"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}, } // Combine multiple assertions AssertStatusCode(t, httpResp, http.StatusOK) AssertContentType(t, httpResp, "application/json") if !AssertJSONBody(t, httpResp) { t.Error("Expected valid JSON body") } }) t.Run("error response validation", func(t *testing.T) { // Simulate an error response body := `{"error":"Invalid request","code":400}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusBadRequest, Header: http.Header{"Content-Type": []string{"application/json"}}, } AssertStatusCode(t, httpResp, http.StatusBadRequest) AssertContentType(t, httpResp, "application/json") if !AssertJSONBody(t, httpResp) { t.Error("Expected valid JSON body for error response") } }) t.Run("streaming response validation", func(t *testing.T) { // Simulate a streaming response body := `data: {"type":"message_start"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"text/event-stream"}}, } AssertStatusCode(t, httpResp, http.StatusOK) AssertContentType(t, httpResp, "text/event-stream") result := ReadResponseBody(t, httpResp) if string(result) != body { t.Errorf("Expected %q, got %q", body, string(result)) } }) } // TestHelperFunctionReturnValues verifies helpers return correct values func TestHelperFunctionReturnValues(t *testing.T) { t.Run("AssertJSONBody returns boolean", func(t *testing.T) { body := `{"test":"data"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } isValid := AssertJSONBody(t, httpResp) if !isValid { t.Error("AssertJSONBody should return true for valid JSON") } }) t.Run("ReadResponseBody returns bytes", func(t *testing.T) { body := `{"test":"data"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } data := ReadResponseBody(t, httpResp) if len(data) == 0 { t.Error("ReadResponseBody should return non-empty data") } if string(data) != body { t.Errorf("ReadResponseBody data mismatch: got %q", string(data)) } }) t.Run("ReadJSONResponse returns map", func(t *testing.T) { body := `{"test":"data"}` resp := httptest.NewRequest("GET", "/", strings.NewReader(body)) httpResp := &http.Response{ Body: resp.Body, StatusCode: http.StatusOK, } parsed := ReadJSONResponse(t, httpResp) if parsed == nil { t.Error("ReadJSONResponse should return non-nil map") } if parsed["test"] != "data" { t.Errorf("ReadJSONResponse parsed value incorrect: got %v", parsed["test"]) } }) }