diff --git a/friendly-captcha-sdk-testserver/fixtures/fixture_test.go b/friendly-captcha-sdk-testserver/fixtures/fixture_test.go new file mode 100644 index 0000000..a410137 --- /dev/null +++ b/friendly-captcha-sdk-testserver/fixtures/fixture_test.go @@ -0,0 +1,114 @@ +package fixtures + +import ( + "encoding/json" + "testing" + + "github.com/guregu/null/v6" +) + +type SiteverifyResponse struct { + Success bool `json:"success"` + Data SiteverifyResponseSuccessData `json:"data,omitempty"` +} + +type SiteverifyResponseSuccessData struct { + EventID string `json:"event_id"` + + Challenge SiteverifyResponseSuccessDataChallenge `json:"challenge"` + + RiskIntelligence null.Value[RiskIntelligenceData] `json:"risk_intelligence"` +} + +type SiteverifyResponseSuccessDataChallenge struct { + Timestamp string `json:"timestamp"` + Origin string `json:"origin"` +} + +type RiskIntelligenceData struct { + // Note: this only contains a subset of the actual API response values. + + RiskScores RiskIntelligenceDataRiskScores `json:"risk_scores"` + + Client RiskIntelligenceDataClient `json:"client"` +} + +type RiskIntelligenceDataRiskScores struct { + Overall uint8 `json:"overall"` + Network uint8 `json:"network"` + Browser uint8 `json:"browser"` +} + +type RiskIntelligenceDataClient struct { + // Note: this only contains a subset of the actual API response values. + + HeaderUserAgent string `json:"header_user_agent"` + Browser null.Value[RiskIntelligenceDataClientBrowser] `json:"browser"` +} + +type RiskIntelligenceDataClientBrowser struct { + // Note: this only contains a subset of the actual API response values. + + ID string `json:"id"` +} + +func TestFixtures(t *testing.T) { + t.Parallel() + + testCases, err := Load("") + if err != nil { + t.Fatalf("Failed to load embedded test cases: %v", err) + } + + // For each fixture make sure it has a name, response and expectation. + for i, tc := range testCases.Tests { + if tc.Name == "" { + t.Errorf("Test case %d has an empty name", i) + } + if tc.Response == "" { + t.Errorf("Test case %d (%s) has an empty response", i, tc.Name) + } + + // Parse as SiteverifyResponse to ensure it's valid JSON. + var svr SiteverifyResponse + err := json.Unmarshal(tc.SiteverifyResponse, &svr) + if err != nil { + // Some responses are completely invalid JSON (e.g., HTML error pages). + // We only validate the ones that are supposed to be valid JSON. + + // We hardcode those that we expect to fail to parse: + invalidJSONCases := map[string]bool{ + "bad_response_200": true, + "bad_response_200_strict": true, + "bad_response_500": true, + "bad_response_400_strict": true, + "empty_string_response_200": true, + "empty_string_response_200_strict": true, + } + if !invalidJSONCases[tc.Name] { + t.Errorf("Test case %d (%s) has invalid siteverify_response JSON: %v", i, tc.Name, err) + } + return + } + + if !svr.Success { // For non-success cases we don't validate further. + continue + } + + ri := svr.Data.RiskIntelligence + + // If risk intelligence data is present, ensure it has expected fields. + if ri.Valid { + if ri.V.Client.HeaderUserAgent == "" { + t.Errorf("Test case %d (%s) has risk intelligence data with empty client header_user_agent", i, tc.Name) + } + + if ri.V.Client.Browser.Valid { + if ri.V.Client.Browser.V.ID == "" { + t.Errorf("Test case %d (%s) has risk intelligence data with empty browser id", i, tc.Name) + } + } + } + + } +} diff --git a/friendly-captcha-sdk-testserver/fixtures/test_cases.json b/friendly-captcha-sdk-testserver/fixtures/test_cases.json index d57c27b..887acad 100644 --- a/friendly-captcha-sdk-testserver/fixtures/test_cases.json +++ b/friendly-captcha-sdk-testserver/fixtures/test_cases.json @@ -1,5 +1,5 @@ { - "version": 1, + "version": 2, "tests": [ { "name": "success", @@ -8,10 +8,12 @@ "siteverify_response": { "success": true, "data": { + "event_id": "ev_2b5ec269-2890-4df2-adbd-45be9974131e", "challenge": { "timestamp": "2023-08-04T13:01:25Z", "origin": "https://example.com" - } + }, + "risk_intelligence": null } }, "siteverify_status_code": 200, @@ -172,10 +174,12 @@ "siteverify_response": { "success": true, "data": { + "event_id": "ev_720d50fd-1e47-4a2d-bc94-f5eeda259f7b", "challenge": { "timestamp": "2023-08-04T13:01:25Z", "origin": "https://example.com" - } + }, + "risk_intelligence": null } }, "siteverify_status_code": 200, @@ -400,6 +404,72 @@ "was_able_to_verify": false, "is_client_error": false } + }, + { + "name": "success_with_risk_intelligence", + "response": "dc2fe149-04ce-45e5-a317-41425942abba", + "strict": false, + "siteverify_response": { + "success": true, + "data": { + "event_id": "ev_dc2fe149-04ce-45e5-a317-41425942abba", + "challenge": { + "timestamp": "2023-08-04T13:01:25Z", + "origin": "https://example.com" + }, + "risk_intelligence": { + "risk_scores": { + "overall": 3, + "network": 3, + "browser": 2 + }, + "client": { + "header_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "browser": { + "id": "chrome" + } + } + } + } + }, + "siteverify_status_code": 200, + "expectation": { + "should_accept": true, + "was_able_to_verify": true, + "is_client_error": false + } + }, + { + "name": "success_with_risk_intelligence_with_null_client_browser", + "response": "c4b0031a-68c1-481e-a40e-fe526b0e0398", + "strict": false, + "siteverify_response": { + "success": true, + "data": { + "event_id": "ev_c4b0031a-68c1-481e-a40e-fe526b0e0398", + "challenge": { + "timestamp": "2023-08-04T13:01:25Z", + "origin": "https://example.com" + }, + "risk_intelligence": { + "risk_scores": { + "overall": 1, + "network": 1, + "browser": 1 + }, + "client": { + "header_user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "browser": null + } + } + } + }, + "siteverify_status_code": 200, + "expectation": { + "should_accept": true, + "was_able_to_verify": true, + "is_client_error": false + } } ] } diff --git a/friendly-captcha-sdk-testserver/go.mod b/friendly-captcha-sdk-testserver/go.mod index b33306b..13863b0 100644 --- a/friendly-captcha-sdk-testserver/go.mod +++ b/friendly-captcha-sdk-testserver/go.mod @@ -1,5 +1,9 @@ module github.com/friendlycaptcha/friendly-captcha-sdk-tooling/friendly-captcha-sdk-testserver -go 1.21 +go 1.22.12 + +toolchain go1.24.3 require github.com/alecthomas/kong v0.8.0 + +require github.com/guregu/null/v6 v6.0.0 // indirect diff --git a/friendly-captcha-sdk-testserver/go.sum b/friendly-captcha-sdk-testserver/go.sum index e3de4d0..20c00ee 100644 --- a/friendly-captcha-sdk-testserver/go.sum +++ b/friendly-captcha-sdk-testserver/go.sum @@ -4,5 +4,7 @@ github.com/alecthomas/kong v0.8.0 h1:ryDCzutfIqJPnNn0omnrgHLbAggDQM2VWHikE1xqK7s github.com/alecthomas/kong v0.8.0/go.mod h1:n1iCIO2xS46oE8ZfYCNDqdR0b0wZNrXAIAqro/2132U= github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE= github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= +github.com/guregu/null/v6 v6.0.0 h1:N14VRS+4di81i1PXRiprbQJ9EM9gqBa0+KVMeS/QSjQ= +github.com/guregu/null/v6 v6.0.0/go.mod h1:hrMIhIfrOZeLPZhROSn149tpw2gHkidAqxoXNyeX3iQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= diff --git a/friendly-captcha-sdk-testserver/main.go b/friendly-captcha-sdk-testserver/main.go index d27dbab..eccf48d 100644 --- a/friendly-captcha-sdk-testserver/main.go +++ b/friendly-captcha-sdk-testserver/main.go @@ -15,8 +15,10 @@ import ( // Usage: go run main.go serve --port 1090 --tests ./test_cases.json // Or just use the defaults: go run main.go serve -const defaultSiteverifyEndpoint = "/api/v2/captcha/siteverify" -const defaultTestsJSONEndpoint = "/api/v1/tests" +const ( + defaultSiteverifyEndpoint = "/api/v2/captcha/siteverify" + defaultTestsJSONEndpoint = "/api/v1/tests" +) var CLI struct { Serve struct { @@ -36,7 +38,6 @@ func main() { default: panic(ctx.Command()) } - } func serve(port int, testsPath string) { @@ -45,7 +46,7 @@ func serve(port int, testsPath string) { panic(err) } - if tf.Version != 1 { + if tf.Version != 2 { panic("Unsupported test file version") } diff --git a/friendly-captcha-sdk-testserver/model/model.go b/friendly-captcha-sdk-testserver/model/model.go index ed91d64..608a9ed 100644 --- a/friendly-captcha-sdk-testserver/model/model.go +++ b/friendly-captcha-sdk-testserver/model/model.go @@ -1,6 +1,8 @@ package model -import "encoding/json" +import ( + "encoding/json" +) // What the SDK should conclude from the API response. type TestCaseExpectation struct {