From 33c036d7d6a4fa3cfce5d58c6e271549c57e1f13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Fri, 23 Jan 2026 12:20:03 +0100 Subject: [PATCH 1/5] feat: dolibarr test case --- tests/addons/test-dolibarr.php | 399 +++++++++++++++++++++++++++++++++ 1 file changed, 399 insertions(+) diff --git a/tests/addons/test-dolibarr.php b/tests/addons/test-dolibarr.php index b1b0fb32..dc6af6ad 100644 --- a/tests/addons/test-dolibarr.php +++ b/tests/addons/test-dolibarr.php @@ -6,9 +6,408 @@ */ use FORMS_BRIDGE\Dolibarr_Form_Bridge; +use FORMS_BRIDGE\Dolibarr_Addon; +use FORMS_BRIDGE\Addon; +use HTTP_BRIDGE\Backend; +use HTTP_BRIDGE\Credential; /** * Dolibarr test case. */ class DolibarrTest extends WP_UnitTestCase { + + /** + * Handles the last intercepted http request data. + * + * @var array + */ + private static $request; + + /** + * Handles the mock response to return. + * + * @var array|null + */ + private static $mock_response; + + /** + * Holds the mocked backend name. + * + * @var string + */ + private const BACKEND_NAME = 'test-dolibarr-backend'; + + /** + * Holds the mocked backend base URL. + * + * @var string + */ + private const BACKEND_URL = 'https://erp.example.coop'; + + /** + * Holds the mocked bridge name. + * + * @var string + */ + private const BRIDGE_NAME = 'test-dolibarr-bridge'; + + /** + * Test backend provider. + * + * @return Backend[] + */ + public static function backends_provider() { + return array( + new Backend( + array( + 'name' => self::BACKEND_NAME, + 'base_url' => self::BACKEND_URL, + 'headers' => array( + array( + 'name' => 'Content-Type', + 'value' => 'application/json', + ), + array( + 'name' => 'Accept', + 'value' => 'application/json', + ), + array( + 'name' => 'DOLAPIKEY', + 'value' => 'test-dolapikey', + ), + ), + ) + ), + ); + } + + /** + * HTTP requests interceptor. + * + * @param mixed $pre Initial pre hook value. + * @param array $args Request arguments. + * @param string $url Request URL. + * + * @return array + */ + public static function pre_http_request( $pre, $args, $url ) { + self::$request = array( + 'args' => $args, + 'url' => $url, + ); + + $http = array( + 'code' => 200, + 'message' => 'OK', + ); + + $method = $args['method'] ?? 'POST'; + + // Parse URL to determine the endpoint being called. + $parsed_url = wp_parse_url( $url ); + $path = $parsed_url['path'] ?? ''; + + // Parse the body to determine the method being called. + $body = array(); + if ( ! empty( $args['body'] ) ) { + if ( is_string( $args['body'] ) ) { + $body = json_decode( $args['body'], true ); + } else { + $body = $args['body']; + } + } + + // Return appropriate mock response based on endpoint. + if ( self::$mock_response ) { + $http = self::$mock_response['http'] ?? $http; + unset( self::$mock_response['http'] ); + $response_body = self::$mock_response; + } else { + $response_body = self::get_mock_response( $method, $path, $body ); + } + + return array( + 'response' => $http, + 'headers' => array( 'Content-Type' => 'application/json' ), + 'cookies' => array(), + 'body' => wp_json_encode( $response_body ), + 'http_response' => null, + ); + } + + /** + * Get mock response based on API endpoint. + * + * @param string $method HTTP method. + * @param string $path API endpoint path. + * @param array $body Request body. + * + * @return array Mock response. + */ + private static function get_mock_response( $method, $path, $body ) { + switch ( $path ) { + case '/api/index.php/status': + return array( 'success' => array( 'code' => 200 ) ); + + case '/api/index.php/explorer/swagger.json': + return array( + 'swagger' => '2.0', + 'paths' => array( + '/contacts' => array( + 'post' => array( + 'parameters' => array( + array( + 'name' => 'lastname', + 'type' => 'string', + 'in' => 'body', + 'required' => true, + ), + array( + 'name' => 'firstname', + 'type' => 'string', + 'in' => 'body', + ), + array( + 'name' => 'email', + 'type' => 'string', + 'in' => 'body', + ), + ), + ), + ), + ), + ); + + case '/api/index.php/contacts': + if ( 'POST' === $method ) { + return array( 'id' => 123456789 ); + } + return array( + array( + 'id' => 1, + 'lastname' => 'Doe', + 'firstname' => 'John', + 'email' => 'john.doe@example.com', + ), + ); + + default: + return array(); + } + } + + /** + * Set up test fixtures. + */ + public function set_up() { + parent::set_up(); + + self::$request = null; + self::$mock_response = null; + + tests_add_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + tests_add_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + tests_add_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + } + + /** + * Tear down test filters. + */ + public function tear_down() { + remove_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + remove_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + remove_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + + parent::tear_down(); + } + + /** + * Test that the addon class exists and has correct constants. + */ + public function test_addon_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Dolibarr_Addon' ) ); + $this->assertEquals( 'Dolibarr', Dolibarr_Addon::TITLE ); + $this->assertEquals( 'dolibarr', Dolibarr_Addon::NAME ); + $this->assertEquals( '\FORMS_BRIDGE\Dolibarr_Form_Bridge', Dolibarr_Addon::BRIDGE ); + } + + /** + * Test that the form bridge class exists. + */ + public function test_form_bridge_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Dolibarr_Form_Bridge' ) ); + } + + /** + * Test bridge validation with valid data. + */ + public function test_bridge_validation() { + $bridge = new Dolibarr_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/index.php/contacts', + 'method' => 'POST', + ) + ); + + $this->assertTrue( $bridge->is_valid ); + } + + /** + * Test bridge validation with invalid data. + */ + public function test_bridge_validation_invalid() { + $bridge = new Dolibarr_Form_Bridge( + array( + 'name' => 'invalid-bridge', + // Missing required fields. + ) + ); + + $this->assertFalse( $bridge->is_valid ); + } + + /** + * Test POST request to create a contact. + */ + public function test_post_create_contact() { + $bridge = new Dolibarr_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/index.php/contacts', + 'method' => 'POST', + ) + ); + + $payload = array( + 'lastname' => 'Doe', + 'firstname' => 'John', + 'email' => 'john.doe@example.com', + ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertEquals( 123456789, $response['data']['id'] ); + } + + /** + * Test GET request to fetch contacts. + */ + public function test_get_contacts() { + $bridge = new Dolibarr_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/index.php/contacts', + 'method' => 'GET', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'lastname', $response['data'][0] ); + $this->assertEquals( 'Doe', $response['data'][0]['lastname'] ); + } + + /** + * Test addon ping method. + */ + public function test_addon_ping() { + $addon = Addon::addon( 'dolibarr' ); + $response = $addon->ping( self::BACKEND_NAME ); + + $this->assertTrue( $response ); + } + + /** + * Test addon get_endpoints method. + */ + public function test_addon_get_endpoints() { + $addon = Addon::addon( 'dolibarr' ); + $endpoints = $addon->get_endpoints( self::BACKEND_NAME ); + + $this->assertIsArray( $endpoints ); + $this->assertContains( '/api/index.php/contacts', $endpoints ); + } + + /** + * Test addon get_endpoint_schema method. + */ + public function test_addon_get_endpoint_schema() { + $addon = Addon::addon( 'dolibarr' ); + $schema = $addon->get_endpoint_schema( + '/api/index.php/contacts', + self::BACKEND_NAME, + 'POST' + ); + + $this->assertIsArray( $schema ); + $this->assertNotEmpty( $schema ); + + $field_names = array_column( $schema, 'name' ); + $this->assertContains( 'lastname', $field_names ); + $this->assertContains( 'firstname', $field_names ); + $this->assertContains( 'email', $field_names ); + } + + /** + * Test error response handling. + */ + public function test_error_response_handling() { + self::$mock_response = array( + 'http' => array( + 'code' => 401, + 'message' => 'Unauthorized', + ), + 'error' => array( + 'code' => 401, + 'message' => 'Unauthorized: Failed to login to API. No parameter \'HTTP_DOLAPIKEY\' on HTTP header (and no parameter DOLAPIKEY in URL).', + ), + 'debug' => array( + 'source' => 'api_access.class.php:219 at authenticate stage', + 'stages' => array( + 'success' => array( 'get', 'route', 'negotiate' ), + 'failure' => array( 'authenticate', 'message' ), + ), + ), + ); + + $bridge = new Dolibarr_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/index.php/contacts', + 'method' => 'GET', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + } + + /** + * Test invalid backend handling. + */ + public function test_invalid_backend() { + $bridge = new Dolibarr_Form_Bridge( + array( + 'name' => 'test-invalid-backend-bridge', + 'backend' => 'non-existent-backend', + 'endpoint' => '/api/index.php/contacts', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + $this->assertEquals( 'invalid_backend', $response->get_error_code() ); + } } From 22acbc2e1142dfc12bf67b320601fb269bf27b92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Fri, 23 Jan 2026 12:20:36 +0100 Subject: [PATCH 2/5] feat: mailchimp test case --- .../mailchimp/class-mailchimp-form-bridge.php | 16 +- tests/addons/test-mailchimp.php | 580 ++++++++++++++++++ 2 files changed, 584 insertions(+), 12 deletions(-) create mode 100644 tests/addons/test-mailchimp.php diff --git a/forms-bridge/addons/mailchimp/class-mailchimp-form-bridge.php b/forms-bridge/addons/mailchimp/class-mailchimp-form-bridge.php index fafed874..e5cc9715 100644 --- a/forms-bridge/addons/mailchimp/class-mailchimp-form-bridge.php +++ b/forms-bridge/addons/mailchimp/class-mailchimp-form-bridge.php @@ -54,13 +54,7 @@ public function submit( $payload = array(), $attachments = array() ) { } if ( 'Member Exists' === $title ) { - if ( - ! preg_match( - '/(?<=lists\/).+(?=\/members)/', - $this->endpoint, - $matches - ) - ) { + if ( ! preg_match( '/(?<=lists\/).+(?=\/members)/', $this->endpoint, $matches ) ) { return $response; } @@ -84,17 +78,15 @@ public function submit( $payload = array(), $attachments = array() ) { return $response; } - $member_id = - $search_response['data']['exact_matches']['members'][0]['id'] ?? null; + $member_id = $search_response['data']['exact_matches']['members'][0]['id'] ?? null; if ( ! $member_id ) { return $response; } $update_endpoint = "/3.0/lists/{$list_id}/members/{$member_id}"; - if ( - strstr( $this->endpoint, 'skip_merge_validation' ) !== false - ) { + + if ( false !== strstr( $this->endpoint, 'skip_merge_validation' ) ) { $update_endpoint .= '?skip_merge_validation=true'; } diff --git a/tests/addons/test-mailchimp.php b/tests/addons/test-mailchimp.php new file mode 100644 index 00000000..c14856e1 --- /dev/null +++ b/tests/addons/test-mailchimp.php @@ -0,0 +1,580 @@ + self::CREDENTIAL_NAME, + 'schema' => 'Basic', + 'client_id' => 'test-client-id', + 'client_secret' => 'test-client-secret', + ) + ), + ); + } + + /** + * Test backend provider. + * + * @return Backend[] + */ + public static function backends_provider() { + return array( + new Backend( + array( + 'name' => self::BACKEND_NAME, + 'base_url' => self::BACKEND_URL, + 'credential' => self::CREDENTIAL_NAME, + 'headers' => array( + array( + 'name' => 'Content-Type', + 'value' => 'application/json', + ), + array( + 'name' => 'Accept', + 'value' => 'application/json', + ), + ), + ) + ), + ); + } + + /** + * HTTP requests interceptor. + * + * @param mixed $pre Initial pre hook value. + * @param array $args Request arguments. + * @param string $url Request URL. + * + * @return array + */ + public static function pre_http_request( $pre, $args, $url ) { + self::$request = array( + 'args' => $args, + 'url' => $url, + ); + + $http = array( + 'code' => 200, + 'message' => 'OK', + ); + + $method = $args['method'] ?? 'POST'; + + // Parse URL to determine the endpoint being called. + $parsed_url = wp_parse_url( $url ); + $path = $parsed_url['path'] ?? ''; + + parse_str( $parsed_url['query'] ?? '', $query ); + + // Parse the body to determine the method being called. + $body = array(); + if ( ! empty( $args['body'] ) ) { + if ( is_string( $args['body'] ) ) { + $body = json_decode( $args['body'], true ); + } else { + $body = $args['body']; + } + } + + // Return appropriate mock response based on endpoint. + if ( self::$mock_response ) { + $http = self::$mock_response['http'] ?? $http; + unset( self::$mock_response['http'] ); + $response_body = self::$mock_response; + + self::$mock_response = null; + } else { + $response_body = self::get_mock_response( $method, $path, $body, $query ); + } + + return array( + 'response' => $http, + 'headers' => array( 'Content-Type' => 'application/json' ), + 'cookies' => array(), + 'body' => wp_json_encode( $response_body ), + 'http_response' => null, + ); + } + + /** + * Get mock response based on API endpoint. + * + * @param string $method HTTP method. + * @param string $path API endpoint path. + * @param array $body Request body. + * @param array $query Search query. + * + * @return array Mock response. + */ + private static function get_mock_response( $method, $path, $body, $query ) { + switch ( $path ) { + case '/developer/spec/marketing.json': + return array( + 'swagger' => '2.0', + 'paths' => array( + '/lists/{list_id}' => array( + 'get' => array( + 'parameters' => array( + array( + 'name' => 'list_id', + 'type' => 'string', + 'in' => 'path', + ), + array( + 'name' => 'skip_merge_validation', + 'type' => 'boolean', + 'in' => 'query', + ), + + ), + ), + 'post' => array( + 'parameters' => array( + array( + 'name' => 'list_id', + 'type' => 'string', + 'in' => 'path', + ), + array( + 'name' => 'skip_merge_validation', + 'type' => 'boolean', + 'in' => 'query', + ), + ), + ), + ), + '/lists/{list_id}/members' => array( + 'get' => array( + 'parameters' => array( + array( + 'name' => 'list_id', + 'type' => 'string', + 'in' => 'path', + ), + array( + 'name' => 'skip_merge_validation', + 'type' => 'boolean', + 'in' => 'query', + ), + ), + ), + 'post' => array( + 'parameters' => array( + array( + 'name' => 'list_id', + 'type' => 'string', + 'in' => 'path', + ), + array( + 'name' => 'skip_merge_validation', + 'type' => 'boolean', + 'in' => 'query', + ), + ), + ), + ), + '/lists/{list_id}/segments' => array( + 'get' => array( + 'parameters' => array( + array( + 'name' => 'list_id', + 'type' => 'string', + 'in' => 'path', + ), + array( + 'name' => 'skip_merge_validation', + 'type' => 'boolean', + 'in' => 'query', + ), + ), + ), + 'post' => array( + 'parameters' => array( + array( + 'name' => 'list_id', + 'type' => 'string', + 'in' => 'path', + ), + array( + 'name' => 'skip_merge_validation', + 'type' => 'boolean', + 'in' => 'query', + ), + ), + ), + ), + ), + ); + + case '/3.0/lists': + return array( + 'lists' => array( + array( + 'id' => '123456789', + 'name' => 'Test List', + ), + ), + ); + + case '/3.0/lists/123456789/members': + if ( 'POST' === $method ) { + return array( + 'id' => '987654321', + 'email_address' => $body['email_address'], + 'status' => 'subscribed', + ); + } + + return array( + 'members' => array( + array( + 'id' => '987654321', + 'email_address' => 'john.doe@example.com', + 'status' => 'subscribed', + ), + ), + ); + + case '/3.0/search-members': + return array( + 'exact_matches' => array( + 'members' => array( + array( + 'id' => '987654321', + 'email_address' => $query['query'], + ), + ), + ), + ); + + default: + return array(); + } + } + + /** + * Set up test fixtures. + */ + public function set_up() { + parent::set_up(); + + self::$request = null; + self::$mock_response = null; + + tests_add_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + tests_add_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + tests_add_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + } + + /** + * Tear down test filters. + */ + public function tear_down() { + remove_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + remove_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + remove_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + + parent::tear_down(); + } + + /** + * Test that the addon class exists and has correct constants. + */ + public function test_addon_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Mailchimp_Addon' ) ); + $this->assertEquals( 'Mailchimp', Mailchimp_Addon::TITLE ); + $this->assertEquals( 'mailchimp', Mailchimp_Addon::NAME ); + $this->assertEquals( '\FORMS_BRIDGE\Mailchimp_Form_Bridge', Mailchimp_Addon::BRIDGE ); + } + + /** + * Test that the form bridge class exists. + */ + public function test_form_bridge_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Mailchimp_Form_Bridge' ) ); + } + + /** + * Test bridge validation with valid data. + */ + public function test_bridge_validation() { + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/3.0/lists/123456789/members', + 'method' => 'POST', + ) + ); + + $this->assertTrue( $bridge->is_valid ); + } + + /** + * Test bridge validation with invalid data. + */ + public function test_bridge_validation_invalid() { + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => 'invalid-bridge', + // Missing required fields. + ) + ); + + $this->assertFalse( $bridge->is_valid ); + } + + /** + * Test POST request to create a member. + */ + public function test_post_create_member() { + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/3.0/lists/123456789/members', + 'method' => 'POST', + ) + ); + + $payload = array( + 'email_address' => 'john.doe@example.com', + 'status' => 'subscribed', + ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertEquals( '987654321', $response['data']['id'] ); + } + + /** + * Test GET request to fetch members. + */ + public function test_get_members() { + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/3.0/lists/123456789/members', + 'method' => 'GET', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'members', $response['data'] ); + $this->assertEquals( 'john.doe@example.com', $response['data']['members'][0]['email_address'] ); + } + + /** + * Test addon ping method. + */ + public function test_addon_ping() { + $addon = Addon::addon( 'mailchimp' ); + $response = $addon->ping( self::BACKEND_NAME ); + + $this->assertTrue( $response ); + } + + /** + * Test addon get_endpoints method. + */ + public function test_addon_get_endpoints() { + $addon = Addon::addon( 'mailchimp' ); + $endpoints = $addon->get_endpoints( self::BACKEND_NAME ); + + $this->assertIsArray( $endpoints ); + $this->assertContains( '/3.0/lists/{list_id}/members', $endpoints ); + } + + /** + * Test addon get_endpoint_schema method. + */ + public function test_addon_get_endpoint_schema() { + $addon = Addon::addon( 'mailchimp' ); + $schema = $addon->get_endpoint_schema( + '/3.0/lists/123456789/members', + self::BACKEND_NAME, + 'GET' + ); + + $this->assertIsArray( $schema ); + $this->assertNotEmpty( $schema ); + + $field_names = array_column( $schema, 'name' ); + $this->assertContains( 'skip_merge_validation', $field_names ); + } + + /** + * Test error response handling. + */ + public function test_error_response_handling() { + self::$mock_response = array( + 'http' => array( + 'code' => 401, + 'message' => 'Unauthorized', + ), + 'title' => 'API Key Invalid', + 'status' => 401, + 'detail' => 'Your request did not include an API key.', + 'type' => 'https://mailchimp.com/developer/marketing/docs/errors/', + 'code' => 'INVALID_TOKEN', + 'instance' => '1234abcd-1234-abcd-1234abcd', + ); + + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/3.0/lists/123456789/members', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + } + + /** + * Test invalid backend handling. + */ + public function test_invalid_backend() { + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => 'test-invalid-backend-bridge', + 'backend' => 'non-existent-backend', + 'endpoint' => '/3.0/lists/123456789/members', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + $this->assertEquals( 'invalid_backend', $response->get_error_code() ); + } + + /** + * Test duplicate member handling. + */ + public function test_duplicate_member_handling() { + self::$mock_response = array( + 'http' => array( + 'code' => 400, + 'message' => 'Bad Request', + ), + 'title' => 'Member Exists', + 'status' => 400, + 'detail' => 'member@example.com has already subscribed to the list', + ); + + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/3.0/lists/123456789/members', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array( 'email_address' => 'member@example.com' ) ); + + // Should not return WP_Error for Member Exists + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + } + + /** + * Test authorization header transformation. + */ + public function test_authorization_header_transformation() { + $bridge = new Mailchimp_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/3.0/lists/123456789/members', + 'method' => 'GET', + ) + ); + + $bridge->submit(); + + // Check that the request was made + $this->assertNotNull( self::$request ); + + // Verify the Authorization header was transformed + $headers = self::$request['args']['headers'] ?? array(); + $this->assertArrayHasKey( 'Authorization', $headers ); + $this->assertStringContainsString( 'Basic', $headers['Authorization'] ); + } +} From 44cf5672cbee9028f55a3254ab3775444b2e2ae0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Fri, 23 Jan 2026 12:21:16 +0100 Subject: [PATCH 3/5] feat: listmonk test case --- .../listmonk/class-listmonk-form-bridge.php | 7 +- tests/addons/test-listmonk.php | 455 ++++++++++++++++++ 2 files changed, 457 insertions(+), 5 deletions(-) diff --git a/forms-bridge/addons/listmonk/class-listmonk-form-bridge.php b/forms-bridge/addons/listmonk/class-listmonk-form-bridge.php index 95aeab68..4e638eb6 100644 --- a/forms-bridge/addons/listmonk/class-listmonk-form-bridge.php +++ b/forms-bridge/addons/listmonk/class-listmonk-form-bridge.php @@ -32,14 +32,11 @@ public function submit( $payload = array(), $attachments = array() ) { $error_response = $response->get_error_data()['response'] ?? null; $code = $error_response['response']['code'] ?? null; - if ( $code !== 409 ) { + if ( 409 !== $code ) { return $response; } - if ( - ! isset( $payload['email'] ) || - $this->endpoint !== '/api/subscribers' - ) { + if ( ! isset( $payload['email'] ) || '/api/subscribers' !== $this->endpoint ) { return $response; } diff --git a/tests/addons/test-listmonk.php b/tests/addons/test-listmonk.php index a599f737..d6969ebb 100644 --- a/tests/addons/test-listmonk.php +++ b/tests/addons/test-listmonk.php @@ -6,9 +6,464 @@ */ use FORMS_BRIDGE\Listmonk_Form_Bridge; +use FORMS_BRIDGE\Listmonk_Addon; +use FORMS_BRIDGE\Addon; +use HTTP_BRIDGE\Backend; +use HTTP_BRIDGE\Credential; /** * Listmonk test case. */ class ListmonkTest extends WP_UnitTestCase { + + /** + * Handles the last intercepted http request data. + * + * @var array + */ + private static $request; + + /** + * Handles the mock response to return. + * + * @var array|null + */ + private static $mock_response; + + /** + * Holds the mocked backend name. + * + * @var string + */ + private const BACKEND_NAME = 'test-listmonk-backend'; + + /** + * Holds the mocked backend base URL. + * + * @var string + */ + private const BACKEND_URL = 'https://listmonk.example.coop'; + + /** + * Holds the mocked credential name. + * + * @var string + */ + private const CREDENTIAL_NAME = 'test-listmonk-credential'; + + /** + * Holds the mocked bridge name. + * + * @var string + */ + private const BRIDGE_NAME = 'test-listmonk-bridge'; + + /** + * Test credential provider. + * + * @return Credential[] + */ + public static function credentials_provider() { + return array( + new Credential( + array( + 'name' => self::CREDENTIAL_NAME, + 'schema' => 'Token', + 'client_id' => 'test-client-id', + 'client_secret' => 'test-client-secret', + ) + ), + ); + } + + /** + * Test backend provider. + * + * @return Backend[] + */ + public static function backends_provider() { + return array( + new Backend( + array( + 'name' => self::BACKEND_NAME, + 'base_url' => self::BACKEND_URL, + 'credential' => self::CREDENTIAL_NAME, + 'headers' => array( + array( + 'name' => 'Content-Type', + 'value' => 'application/json', + ), + array( + 'name' => 'Accept', + 'value' => 'application/json', + ), + ), + ) + ), + ); + } + + /** + * HTTP requests interceptor. + * + * @param mixed $pre Initial pre hook value. + * @param array $args Request arguments. + * @param string $url Request URL. + * + * @return array + */ + public static function pre_http_request( $pre, $args, $url ) { + self::$request = array( + 'args' => $args, + 'url' => $url, + ); + + $http = array( + 'code' => 200, + 'message' => 'OK', + ); + + $method = $args['method'] ?? 'POST'; + + // Parse URL to determine the endpoint being called. + $parsed_url = wp_parse_url( $url ); + $path = $parsed_url['path'] ?? ''; + + // Parse the body to determine the method being called. + $body = array(); + if ( ! empty( $args['body'] ) ) { + if ( is_string( $args['body'] ) ) { + $body = json_decode( $args['body'], true ); + } else { + $body = $args['body']; + } + } + + // Return appropriate mock response based on endpoint. + if ( self::$mock_response ) { + $http = self::$mock_response['http'] ?? $http; + unset( self::$mock_response['http'] ); + $response_body = self::$mock_response; + + self::$mock_response = null; + } else { + $response_body = self::get_mock_response( $method, $path, $body ); + } + + return array( + 'response' => $http, + 'headers' => array( 'Content-Type' => 'application/json' ), + 'cookies' => array(), + 'body' => wp_json_encode( $response_body ), + 'http_response' => null, + ); + } + + /** + * Get mock response based on API endpoint. + * + * @param string $method HTTP method. + * @param string $path API endpoint path. + * @param array $body Request body. + * + * @return array Mock response. + */ + private static function get_mock_response( $method, $path, $body ) { + switch ( $path ) { + case '/api/lists': + return array( + 'data' => array( + 'results' => array( + array( + 'id' => 1, + 'name' => 'Test List', + ), + ), + ), + ); + + case '/api/subscribers': + if ( 'POST' === $method ) { + return array( + 'data' => array( + 'id' => 123456789, + 'email' => $body['email'], + 'name' => $body['name'], + 'status' => 'enabled', + ), + ); + } + return array( + 'data' => array( + 'results' => array( + array( + 'id' => 1, + 'email' => 'john.doe@example.com', + 'name' => 'John Doe', + 'status' => 'enabled', + ), + ), + ), + ); + + default: + return array(); + } + } + + /** + * Set up test fixtures. + */ + public function set_up() { + parent::set_up(); + + self::$request = null; + self::$mock_response = null; + + tests_add_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + tests_add_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + tests_add_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + } + + /** + * Tear down test filters. + */ + public function tear_down() { + remove_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + remove_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + remove_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + + parent::tear_down(); + } + + /** + * Test that the addon class exists and has correct constants. + */ + public function test_addon_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Listmonk_Addon' ) ); + $this->assertEquals( 'Listmonk', Listmonk_Addon::TITLE ); + $this->assertEquals( 'listmonk', Listmonk_Addon::NAME ); + $this->assertEquals( '\FORMS_BRIDGE\Listmonk_Form_Bridge', Listmonk_Addon::BRIDGE ); + } + + /** + * Test that the form bridge class exists. + */ + public function test_form_bridge_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Listmonk_Form_Bridge' ) ); + } + + /** + * Test bridge validation with valid data. + */ + public function test_bridge_validation() { + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/subscribers', + 'method' => 'POST', + ) + ); + + $this->assertTrue( $bridge->is_valid ); + } + + /** + * Test bridge validation with invalid data. + */ + public function test_bridge_validation_invalid() { + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => 'invalid-bridge', + // Missing required fields. + ) + ); + + $this->assertFalse( $bridge->is_valid ); + } + + /** + * Test POST request to create a subscriber. + */ + public function test_post_create_subscriber() { + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/subscribers', + 'method' => 'POST', + ) + ); + + $payload = array( + 'email' => 'john.doe@example.com', + 'name' => 'John Doe', + ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertEquals( 123456789, $response['data']['data']['id'] ); + } + + /** + * Test GET request to fetch subscribers. + */ + public function test_get_subscribers() { + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/subscribers', + 'method' => 'GET', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'results', $response['data']['data'] ); + $this->assertEquals( 'john.doe@example.com', $response['data']['data']['results'][0]['email'] ); + } + + /** + * Test addon ping method. + */ + public function test_addon_ping() { + $addon = Addon::addon( 'listmonk' ); + $response = $addon->ping( self::BACKEND_NAME ); + + $this->assertTrue( $response ); + } + + /** + * Test addon get_endpoints method. + */ + public function test_addon_get_endpoints() { + $addon = Addon::addon( 'listmonk' ); + $endpoints = $addon->get_endpoints( self::BACKEND_NAME ); + + $this->assertIsArray( $endpoints ); + $this->assertContains( '/api/subscribers', $endpoints ); + } + + /** + * Test addon get_endpoint_schema method. + */ + public function test_addon_get_endpoint_schema() { + $addon = Addon::addon( 'listmonk' ); + $schema = $addon->get_endpoint_schema( + '/api/subscribers', + self::BACKEND_NAME, + 'POST' + ); + + $this->assertIsArray( $schema ); + $this->assertNotEmpty( $schema ); + + $field_names = array_column( $schema, 'name' ); + $this->assertContains( 'email', $field_names ); + $this->assertContains( 'name', $field_names ); + $this->assertContains( 'status', $field_names ); + } + + /** + * Test error response handling. + */ + public function test_error_response_handling() { + self::$mock_response = array( + 'http' => array( + 'code' => 401, + 'message' => 'Unauthorized', + ), + 'message' => 'Invalid authentication token', + ); + + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/subscribers', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + } + + /** + * Test invalid backend handling. + */ + public function test_invalid_backend() { + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => 'test-invalid-backend-bridge', + 'backend' => 'non-existent-backend', + 'endpoint' => '/api/subscribers', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + $this->assertEquals( 'invalid_backend', $response->get_error_code() ); + } + + /** + * Test duplicate subscriber handling. + */ + public function test_duplicate_subscriber_handling() { + self::$mock_response = array( + 'http' => array( + 'code' => 409, + 'message' => 'Conflict', + ), + 'message' => 'Subscriber already exists', + ); + + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/subscribers', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array( 'email' => 'duplicate@example.com' ) ); + + // Should not return WP_Error for DUPLICATE_SUBSCRIBER + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + } + + /** + * Test authorization header transformation. + */ + public function test_authorization_header_transformation() { + $bridge = new Listmonk_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/api/subscribers', + 'method' => 'GET', + ) + ); + + $bridge->submit(); + + // Check that the request was made + $this->assertNotNull( self::$request ); + + // Verify the Authorization header was transformed + $headers = self::$request['args']['headers'] ?? array(); + $this->assertArrayHasKey( 'Authorization', $headers ); + $this->assertStringContainsString( 'token', $headers['Authorization'] ); + } } From bebf1eeb7af6cb9f68601405396fbb3278d92673 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Fri, 23 Jan 2026 12:21:54 +0100 Subject: [PATCH 4/5] fix: invalid_backend form bridge error reporting --- forms-bridge/includes/class-form-bridge.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/forms-bridge/includes/class-form-bridge.php b/forms-bridge/includes/class-form-bridge.php index 1f3bab3b..aece3ed4 100644 --- a/forms-bridge/includes/class-form-bridge.php +++ b/forms-bridge/includes/class-form-bridge.php @@ -454,7 +454,11 @@ public function submit( $payload = array(), $attachments = array() ) { $backend = $this->backend(); if ( ! $backend ) { - return new WP_Error( 'invalid_bridge' ); + return new WP_Error( + 'invalid_backend', + 'The bridge does not have a valid backend', + (array) $this->data, + ); } $method = $this->method; From 924f7ae25d25abec9ddba528578fea49b49d49d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lucas=20Garc=C3=ADa?= Date: Fri, 23 Jan 2026 12:22:28 +0100 Subject: [PATCH 5/5] feat: brevo addon test case --- .../addons/brevo/class-brevo-addon.php | 32 +- .../addons/zoho/class-zoho-form-bridge.php | 3 +- tests/addons/test-brevo.php | 456 ++++++++++++++++++ 3 files changed, 464 insertions(+), 27 deletions(-) diff --git a/forms-bridge/addons/brevo/class-brevo-addon.php b/forms-bridge/addons/brevo/class-brevo-addon.php index 99ff6be1..378c4175 100644 --- a/forms-bridge/addons/brevo/class-brevo-addon.php +++ b/forms-bridge/addons/brevo/class-brevo-addon.php @@ -138,42 +138,22 @@ function ( $path ) { * @return array */ public function get_endpoint_schema( $endpoint, $backend, $method = null ) { - $bytes = random_bytes( 16 ); - $bytes[6] = chr( ord( $bytes[6] ) & 0x0f | 0x40 ); - $bytes[8] = chr( ord( $bytes[8] ) & 0x3f | 0x80 ); - $uuid = vsprintf( '%s%s-%s-%s-%s-%s%s%s', str_split( bin2hex( $bytes ), 4 ) ); + if ( ! function_exists( 'yaml_parse' ) ) { + return array(); + } - $response = wp_remote_get( - self::OAS_URL, - array( - 'headers' => array( - 'Accept' => 'application/json', - 'Host' => 'developers.brevo.com', - 'Referer' => 'https://developers.brevo.com/reference/get_companies', - 'Alt-Used' => 'developers.brevo.com', - 'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64; rv:144.0) Gecko/20100101 Firefox/144.0', - 'X-Requested-With' => 'XMLHttpRequest', - ), - 'cookies' => array( - 'anonymous_id' => $uuid, - 'first_referrer' => 'https://app.brevo.com/', - 'pscd' => 'get.brevo.com', - 'readme_language' => 'shell', - 'readme_library' => '{%22shell%22:%22curl%22}', - ), - ) - ); + $response = wp_remote_get( self::OAS_URL ); if ( is_wp_error( $response ) ) { return array(); } - $data = json_decode( $response['body'], true ); + $data = yaml_parse( $response['body'] ); if ( ! $data ) { return array(); } - $oa_explorer = new OpenAPI( $data['oasDefinition'] ); + $oa_explorer = new OpenAPI( $data ); $method = strtolower( $method ?? 'post' ); $path = preg_replace( '/^\/v\d+/', '', $endpoint ); diff --git a/forms-bridge/addons/zoho/class-zoho-form-bridge.php b/forms-bridge/addons/zoho/class-zoho-form-bridge.php index 9a6c72d7..dc067a79 100644 --- a/forms-bridge/addons/zoho/class-zoho-form-bridge.php +++ b/forms-bridge/addons/zoho/class-zoho-form-bridge.php @@ -75,7 +75,8 @@ function ( $headers, $backend ) { if ( ! $backend ) { return new WP_Error( 'invalid_backend', - 'The bridge does not have a valid backend' + 'The bridge does not have a valid backend', + (array) $this->data, ); } diff --git a/tests/addons/test-brevo.php b/tests/addons/test-brevo.php index b929c011..ddd588fe 100644 --- a/tests/addons/test-brevo.php +++ b/tests/addons/test-brevo.php @@ -6,9 +6,465 @@ */ use FORMS_BRIDGE\Brevo_Form_Bridge; +use FORMS_BRIDGE\Brevo_Addon; +use FORMS_BRIDGE\Addon; +use HTTP_BRIDGE\Backend; +use HTTP_BRIDGE\Credential; /** * Brevo test case. */ class BrevoTest extends WP_UnitTestCase { + + /** + * Handles the last intercepted http request data. + * + * @var array + */ + private static $request; + + /** + * Handles the mock response to return. + * + * @var array|null + */ + private static $mock_response; + + /** + * Holds the mocked backend name. + * + * @var string + */ + private const BACKEND_NAME = 'test-brevo-backend'; + + /** + * Holds the mocked backend base URL. + * + * @var string + */ + private const BACKEND_URL = 'https://api.brevo.com'; + + /** + * Holds the mocked bridge name. + * + * @var string + */ + private const BRIDGE_NAME = 'test-brevo-bridge'; + + /** + * Test backend provider. + * + * @return Backend[] + */ + public static function backends_provider() { + return array( + new Backend( + array( + 'name' => self::BACKEND_NAME, + 'base_url' => self::BACKEND_URL, + 'headers' => array( + array( + 'name' => 'Content-Type', + 'value' => 'application/json', + ), + array( + 'name' => 'Accept', + 'value' => 'application/json', + ), + array( + 'name' => 'api-key', + 'value' => 'test-brevo-api-key', + ), + ), + ) + ), + ); + } + + /** + * HTTP requests interceptor. + * + * @param mixed $pre Initial pre hook value. + * @param array $args Request arguments. + * @param string $url Request URL. + * + * @return array + */ + public static function pre_http_request( $pre, $args, $url ) { + self::$request = array( + 'args' => $args, + 'url' => $url, + ); + + $http = array( + 'code' => 200, + 'message' => 'OK', + ); + + $method = $args['method'] ?? 'POST'; + + // Parse URL to determine the endpoint being called. + $parsed_url = wp_parse_url( $url ); + $path = $parsed_url['path'] ?? ''; + + // Parse the body to determine the method being called. + $body = array(); + if ( ! empty( $args['body'] ) ) { + if ( is_string( $args['body'] ) ) { + $body = json_decode( $args['body'], true ); + } else { + $body = $args['body']; + } + } + + // Return appropriate mock response based on endpoint. + if ( self::$mock_response ) { + $http = self::$mock_response['http'] ?? $http; + unset( self::$mock_response['http'] ); + $response_body = self::$mock_response; + + self::$mock_response = null; + } else { + $response_body = self::get_mock_response( $method, $path, $body ); + } + + return array( + 'response' => $http, + 'headers' => array( 'Content-Type' => 'application/json' ), + 'cookies' => array(), + 'body' => wp_json_encode( $response_body ), + 'http_response' => null, + ); + } + + /** + * Get mock response based on API endpoint. + * + * @param string $method HTTP method. + * @param string $path API endpoint path. + * @param array $body Request body. + * + * @return array Mock response. + */ + private static function get_mock_response( $method, $path, $body ) { + switch ( $path ) { + case '/v3/swagger_definition_v3.yml': + return array( + 'openapi' => '3.0.1', + 'paths' => array( + '/contacts' => array( + 'get' => array( + 'parameters' => array( + array( + 'in' => 'query', + 'name' => 'limit', + 'schema' => array( 'type' => 'integer' ), + ), + ), + ), + 'post' => array( + 'requestBody' => array( + 'content' => array( + 'application/json' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array( + 'email' => array( 'type' => 'string' ), + 'listIds' => array( 'type' => 'array' ), + ), + ), + ), + ), + ), + ), + ), + '/contacts/list' => array( + 'get' => array( + 'parameters' => array( + array( + 'in' => 'query', + 'name' => 'limit', + 'schema' => array( 'type' => 'integer' ), + ), + ), + ), + 'post' => array( + 'requestBody' => array( + 'content' => array( + 'application/json' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => array( + 'folderId' => array( 'type' => 'integer' ), + 'name' => array( 'type' => 'string' ), + ), + ), + ), + ), + ), + ), + ), + ), + ); + + case '/v3/contacts/lists': + return array( + 'lists' => array( + array( + 'id' => 1, + 'name' => 'Test List', + ), + ), + ); + + case '/v3/contacts': + if ( 'POST' === $method ) { + return array( + 'id' => 123456789, + 'email' => $body['email'], + ); + } + + return array( + 'contacts' => array( + array( + 'id' => 123456789, + 'email' => 'john.doe@example.com', + ), + ), + ); + + default: + return array(); + } + } + + /** + * Set up test fixtures. + */ + public function set_up() { + parent::set_up(); + + self::$request = null; + self::$mock_response = null; + + tests_add_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + tests_add_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + tests_add_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + } + + /** + * Tear down test filters. + */ + public function tear_down() { + remove_filter( 'http_bridge_credentials', array( self::class, 'credentials_provider' ), 10, 0 ); + remove_filter( 'http_bridge_backends', array( self::class, 'backends_provider' ), 10, 0 ); + remove_filter( 'pre_http_request', array( self::class, 'pre_http_request' ), 10, 3 ); + + parent::tear_down(); + } + + /** + * Test that the addon class exists and has correct constants. + */ + public function test_addon_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Brevo_Addon' ) ); + $this->assertEquals( 'Brevo', Brevo_Addon::TITLE ); + $this->assertEquals( 'brevo', Brevo_Addon::NAME ); + $this->assertEquals( '\FORMS_BRIDGE\Brevo_Form_Bridge', Brevo_Addon::BRIDGE ); + } + + /** + * Test that the form bridge class exists. + */ + public function test_form_bridge_class_exists() { + $this->assertTrue( class_exists( 'FORMS_BRIDGE\Brevo_Form_Bridge' ) ); + } + + /** + * Test bridge validation with valid data. + */ + public function test_bridge_validation() { + $bridge = new Brevo_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/v3/contacts', + 'method' => 'POST', + ) + ); + + $this->assertTrue( $bridge->is_valid ); + } + + /** + * Test bridge validation with invalid data. + */ + public function test_bridge_validation_invalid() { + $bridge = new Brevo_Form_Bridge( + array( + 'name' => 'invalid-bridge', + // Missing required fields. + ) + ); + + $this->assertFalse( $bridge->is_valid ); + } + + /** + * Test POST request to create a contact. + */ + public function test_post_create_contact() { + $bridge = new Brevo_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/v3/contacts', + 'method' => 'POST', + ) + ); + + $payload = array( 'email' => 'john.doe@example.com' ); + + $response = $bridge->submit( $payload ); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertEquals( 123456789, $response['data']['id'] ); + } + + /** + * Test GET request to fetch contacts. + */ + public function test_get_contacts() { + $bridge = new Brevo_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/v3/contacts', + 'method' => 'GET', + ) + ); + + $response = $bridge->submit(); + + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + $this->assertArrayHasKey( 'contacts', $response['data'] ); + $this->assertEquals( 'john.doe@example.com', $response['data']['contacts'][0]['email'] ); + } + + /** + * Test addon ping method. + */ + public function test_addon_ping() { + $addon = Addon::addon( 'brevo' ); + $response = $addon->ping( self::BACKEND_NAME ); + + $this->assertTrue( $response ); + } + + /** + * Test addon get_endpoints method. + */ + public function test_addon_get_endpoints() { + $addon = Addon::addon( 'brevo' ); + $endpoints = $addon->get_endpoints( self::BACKEND_NAME ); + + $this->assertIsArray( $endpoints ); + $this->assertContains( '/v3/contacts', $endpoints ); + } + + /** + * Test addon get_endpoint_schema method. + */ + public function test_addon_get_endpoint_schema() { + $addon = Addon::addon( 'brevo' ); + $schema = $addon->get_endpoint_schema( + '/v3/contacts', + self::BACKEND_NAME, + 'POST' + ); + + $this->assertIsArray( $schema ); + $this->assertNotEmpty( $schema ); + + $field_names = array_column( $schema, 'name' ); + $this->assertContains( 'email', $field_names ); + } + + /** + * Test error response handling. + */ + public function test_error_response_handling() { + self::$mock_response = array( + 'http' => array( + 'code' => 401, + 'message' => 'Unauthorized', + ), + 'code' => 'unauthorized', + 'message' => 'Key not found', + ); + + $bridge = new Brevo_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/v3/contacts', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + } + + /** + * Test invalid backend handling. + */ + public function test_invalid_backend() { + $bridge = new Brevo_Form_Bridge( + array( + 'name' => 'test-invalid-backend-bridge', + 'backend' => 'non-existent-backend', + 'endpoint' => '/v3/contacts', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array() ); + + $this->assertTrue( is_wp_error( $response ) ); + $this->assertEquals( 'invalid_backend', $response->get_error_code() ); + } + + /** + * Test duplicate contact handling. + */ + public function test_duplicate_contact_handling() { + self::$mock_response = array( + 'http' => array( + 'code' => 425, + 'message' => 'TOO_EARLY', + ), + 'code' => 'duplicate_parameter', + 'message' => 'email is already associated with another Contact', + ); + + $bridge = new Brevo_Form_Bridge( + array( + 'name' => self::BRIDGE_NAME, + 'backend' => self::BACKEND_NAME, + 'endpoint' => '/v3/contacts', + 'method' => 'POST', + ) + ); + + $response = $bridge->submit( array( 'email' => 'duplicate@example.com' ) ); + + // Should not return WP_Error for duplicate_parameter + $this->assertFalse( is_wp_error( $response ) ); + $this->assertArrayHasKey( 'data', $response ); + } }