diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b76d74..5784f21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.0.0 + +- Add SignatureValidationException + ## 3.0.0 - Deploy to Google Cloud workflow diff --git a/lib/src/impl/blockfrost_webhook_handler.dart b/lib/src/impl/blockfrost_webhook_handler.dart index f10ae51..b07de0c 100644 --- a/lib/src/impl/blockfrost_webhook_handler.dart +++ b/lib/src/impl/blockfrost_webhook_handler.dart @@ -28,13 +28,21 @@ class BlockfrostWebhookHandler implements WebhookHandler { } // Validate signature - if (!validator.validate( - signatureHeader: signatureHeader, - requestPayload: requestPayload, - secretAuthToken: secretToken, - )) { - return Response(400, body: 'Signature validation failed!'); + try { + validator.validate( + requestPayload: requestPayload, + signatureHeader: signatureHeader, + secretAuthToken: secretToken); + } on SignatureValidationException catch (e) { + return Response( + 400, + // Or whatever single status code you prefer for all validation failures + body: 'Signature validation failed! ${e.toString()}', + ); + } catch (e) { + return Response.internalServerError(body: 'Signature validation failed!'); } + // Process payload try { processor.process(requestPayload); diff --git a/pubspec.lock b/pubspec.lock index 0e2685d..ef3124f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -38,7 +38,7 @@ packages: description: path: "." ref: "feature/blockfrost_secure_webhooks" - resolved-ref: d0af10d45c0158656da5798ee4d7e5cc26254ada + resolved-ref: eb1f853fbd56ea4f67bff2e47f3f48d911c8f7a9 url: "https://github.com/andro-devs/blockfrost-dart.git" source: git version: "1.0.0" diff --git a/test/blockfrost_webhook_handler_test.dart b/test/blockfrost_webhook_handler_test.dart index d9407ec..ccab3f2 100644 --- a/test/blockfrost_webhook_handler_test.dart +++ b/test/blockfrost_webhook_handler_test.dart @@ -54,6 +54,25 @@ void main() { )).thenReturn(value); } + // Define the common stubbing logic + void stubValidatorToThrowSignatureValidationException() { + when(() => mockValidator.validate( + signatureHeader: any(named: 'signatureHeader'), + requestPayload: any(named: 'requestPayload'), + secretAuthToken: testSecret, + )) + .thenThrow( + SignatureValidationException('Invalid', header: "", payload: "")); + } + + void stubValidatorToThrowException() { + when(() => mockValidator.validate( + signatureHeader: any(named: 'signatureHeader'), + requestPayload: any(named: 'requestPayload'), + secretAuthToken: testSecret, + )).thenThrow(Exception()); + } + // Define the common stubbing logic void stubProcessorToThrowException() { when(() => mockProcessor.process(any())).thenThrow(Exception()); @@ -95,21 +114,58 @@ void main() { // --- Test Case 2: Validation Failure --- test('Should return 400 when validation fails (Rejection scenario)', () async { - // STUB: Inject a validator that ALWAYS returns FALSE (simulates invalid signature) - stubValidatorToReturn(false); + // Use the revised stub that throws a concrete exception + stubValidatorToThrowSignatureValidationException(); final request = createMockRequest( body: testBodyBlock, headers: {'blockfrost-signature': validSignatureHeader}, ); + // Call the handler, which will catch the thrown exception and return a Response final response = await handler.handleWebhook( - request: request, - secretToken: testSecret, - validator: mockValidator, - processor: mockProcessor); + request: request, + secretToken: testSecret, + validator: mockValidator, + processor: mockProcessor, + ); expect(response.statusCode, 400); - expect(await response.readAsString(), 'Signature validation failed!'); + final responseBody = await response.readAsString(); + expect(responseBody, startsWith('Signature validation failed!')); + expect(responseBody, contains('SignatureValidationException: Invalid')); + expect(responseBody, contains('Header:')); + expect(responseBody, contains('Payload:')); + + // Verify that the validator was called once + verify(() => mockValidator.validate( + signatureHeader: validSignatureHeader, + requestPayload: testBodyBlock, + secretAuthToken: testSecret, + )).called(1); + + // Verify that the processor was never reached + verifyZeroInteractions(mockProcessor); + }); + + // --- Test Case 3: Validation Failure --- + test('Should return 500 when validation fails with internal error', + () async { + // Use the revised stub that throws a concrete exception + stubValidatorToThrowException(); + + final request = createMockRequest( + body: testBodyBlock, + headers: {'blockfrost-signature': validSignatureHeader}, + ); + + // Call the handler, which will catch the thrown exception and return a Response + final response = await handler.handleWebhook( + request: request, + secretToken: testSecret, + validator: mockValidator, + processor: mockProcessor, + ); + expect(response.statusCode, 500); // Verify the mock was called to ensure we tested the correct path verify(() => mockValidator.validate( @@ -122,7 +178,7 @@ void main() { verifyZeroInteractions(mockProcessor); }); - // --- Test Case 3: Empty payload --- + // --- Test Case 4: Empty payload --- test('Should return 400 if the payload is empty', () async { // STUB: Inject a validator that ALWAYS returns TRUE (simulates valid signature) stubValidatorToReturn(true); @@ -141,7 +197,7 @@ void main() { verifyZeroInteractions(mockProcessor); }); - // --- Test Case 4: Successful Reception of an transaction webhook --- + // --- Test Case 5: Successful Reception of an transaction webhook --- test('Should return 200 when validation succeeds and type is transaction', () async { stubValidatorToReturn( @@ -164,7 +220,7 @@ void main() { verify(() => mockProcessor.process(testBodyTx)).called(1); }); - // --- Test Case 5: Successful Reception of an transaction webhook --- + // --- Test Case 6: Successful Reception of an transaction webhook --- test('Should return 200 when validation succeeds and type is transaction', () async { // STUB: Inject a validator that ALWAYS returns TRUE (simulates valid signature) @@ -187,7 +243,7 @@ void main() { verify(() => mockProcessor.process(testBodyBlock)).called(1); }); - // --- Test Case 6: Invalid JSON After Validation --- + // --- Test Case 7: Invalid JSON After Validation --- test('Should return 500 if the validated payload is not valid JSON', () async { stubProcessorToThrowException();