From 4cd1c49254dfe3c2e5f7094cc16e867c0e5ac61e Mon Sep 17 00:00:00 2001 From: Guido Zuidhof Date: Tue, 27 Jan 2026 11:46:08 +0100 Subject: [PATCH 1/4] Add `risk_intelligence` and `event_id` to fixtures --- .../fixtures/fixture_test.go | 116 ++++++++++++++ .../fixtures/test_cases.json | 148 +++++++++++++++--- friendly-captcha-sdk-testserver/go.mod | 6 +- friendly-captcha-sdk-testserver/go.sum | 2 + friendly-captcha-sdk-testserver/main.go | 9 +- .../model/model.go | 4 +- 6 files changed, 253 insertions(+), 32 deletions(-) create mode 100644 friendly-captcha-sdk-testserver/fixtures/fixture_test.go 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..a495a8d --- /dev/null +++ b/friendly-captcha-sdk-testserver/fixtures/fixture_test.go @@ -0,0 +1,116 @@ +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"` + + RiskIntelligence null.Value[RiskIntelligenceData] `json:"risk_intelligence,omitempty"` +} + +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..2043e78 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,17 +8,21 @@ "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, "expectation": { "should_accept": true, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "ev_2b5ec269-2890-4df2-adbd-45be9974131e", + "risk_intelligence_value": null } }, { @@ -36,7 +40,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -54,7 +59,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -72,7 +78,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -90,7 +97,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -108,7 +116,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -126,7 +135,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -144,7 +154,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -162,7 +173,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -172,17 +184,21 @@ "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, "expectation": { "should_accept": true, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "ev_720d50fd-1e47-4a2d-bc94-f5eeda259f7b", + "risk_intelligence_value": null } }, { @@ -200,7 +216,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -218,7 +235,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -236,7 +254,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -254,7 +273,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -272,7 +292,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -290,7 +311,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -308,7 +330,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -326,7 +349,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true + "is_client_error": true, + "event_id": "" } }, { @@ -338,7 +362,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -350,7 +375,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -362,7 +388,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -374,7 +401,8 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -386,7 +414,8 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": false + "is_client_error": false, + "event_id": "" } }, { @@ -398,8 +427,75 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, + "is_client_error": false, + "event_id": "" + } + }, + { + "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 { From 63986a3f1e1fdcb3e98fdb4dc5a95ecf6cbeff0f Mon Sep 17 00:00:00 2001 From: Guido Zuidhof Date: Tue, 27 Jan 2026 14:20:12 +0100 Subject: [PATCH 2/4] Fix wrong additional field in test typing --- friendly-captcha-sdk-testserver/fixtures/fixture_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/friendly-captcha-sdk-testserver/fixtures/fixture_test.go b/friendly-captcha-sdk-testserver/fixtures/fixture_test.go index a495a8d..a410137 100644 --- a/friendly-captcha-sdk-testserver/fixtures/fixture_test.go +++ b/friendly-captcha-sdk-testserver/fixtures/fixture_test.go @@ -23,8 +23,6 @@ type SiteverifyResponseSuccessData struct { type SiteverifyResponseSuccessDataChallenge struct { Timestamp string `json:"timestamp"` Origin string `json:"origin"` - - RiskIntelligence null.Value[RiskIntelligenceData] `json:"risk_intelligence,omitempty"` } type RiskIntelligenceData struct { From 20ff24cf5aac9d0d51e2876fe9b5b0cfcf13a2c4 Mon Sep 17 00:00:00 2001 From: Guido Zuidhof Date: Tue, 27 Jan 2026 16:10:57 +0100 Subject: [PATCH 3/4] Clean up unused expectation fields --- .../fixtures/test_cases.json | 74 ++++++------------- 1 file changed, 24 insertions(+), 50 deletions(-) diff --git a/friendly-captcha-sdk-testserver/fixtures/test_cases.json b/friendly-captcha-sdk-testserver/fixtures/test_cases.json index 2043e78..50bf762 100644 --- a/friendly-captcha-sdk-testserver/fixtures/test_cases.json +++ b/friendly-captcha-sdk-testserver/fixtures/test_cases.json @@ -20,9 +20,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "ev_2b5ec269-2890-4df2-adbd-45be9974131e", - "risk_intelligence_value": null + "is_client_error": false } }, { @@ -40,8 +38,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -59,8 +56,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -78,8 +74,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -97,8 +92,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -116,8 +110,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -135,8 +128,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -154,8 +146,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -173,8 +164,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -196,9 +186,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "ev_720d50fd-1e47-4a2d-bc94-f5eeda259f7b", - "risk_intelligence_value": null + "is_client_error": false } }, { @@ -216,8 +204,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -235,8 +222,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -254,8 +240,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -273,8 +258,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -292,8 +276,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -311,8 +294,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -330,8 +312,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": true, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -349,8 +330,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": true, - "event_id": "" + "is_client_error": true } }, { @@ -362,8 +342,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -375,8 +354,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -388,8 +366,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -401,8 +378,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -414,8 +390,7 @@ "expectation": { "should_accept": true, "was_able_to_verify": false, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { @@ -427,8 +402,7 @@ "expectation": { "should_accept": false, "was_able_to_verify": false, - "is_client_error": false, - "event_id": "" + "is_client_error": false } }, { From 76f786f7743dab54409583fa11c95981f9175662 Mon Sep 17 00:00:00 2001 From: Guido Zuidhof Date: Tue, 27 Jan 2026 16:11:26 +0100 Subject: [PATCH 4/4] Format --- friendly-captcha-sdk-testserver/fixtures/test_cases.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly-captcha-sdk-testserver/fixtures/test_cases.json b/friendly-captcha-sdk-testserver/fixtures/test_cases.json index 50bf762..887acad 100644 --- a/friendly-captcha-sdk-testserver/fixtures/test_cases.json +++ b/friendly-captcha-sdk-testserver/fixtures/test_cases.json @@ -469,7 +469,7 @@ "should_accept": true, "was_able_to_verify": true, "is_client_error": false + } } - } ] }