Commit 55477aa43e for woocommerce
commit 55477aa43e6fd5646156d996d4c99b6ffcbe4eb2
Author: Hannah Tinkler <hannah.tinkler@gmail.com>
Date: Fri Feb 13 15:29:30 2026 +0000
Persist device_locale and metadata through the full pipeline. (#63246)
* Persist device_locale and metadata through the full pipeline.
- Add device_locale and metadata to PushToken entity (properties, getters, constructor, required params).
- Pass both fields from REST controller to data store in create and update paths.
- Add both fields to data store SUPPORTED_META, read, create, and lookup methods.
- Default to en_US locale and empty JSON for legacy tokens without the new fields.
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php b/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php
index a1d7db69ee..b308fec69d 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Controllers/PushTokenRestController.php
@@ -27,8 +27,6 @@ use WP_Http;
* tokens.
*
* @since 10.6.0
- *
- * @internal
*/
class PushTokenRestController extends RestApiControllerBase {
/**
@@ -105,11 +103,13 @@ class PushTokenRestController extends RestApiControllerBase {
public function create( WP_REST_Request $request ) {
try {
$data = array(
- 'user_id' => get_current_user_id(),
- 'token' => $request->get_param( 'token' ),
- 'platform' => $request->get_param( 'platform' ),
- 'device_uuid' => $request->get_param( 'device_uuid' ),
- 'origin' => $request->get_param( 'origin' ),
+ 'user_id' => get_current_user_id(),
+ 'token' => $request->get_param( 'token' ),
+ 'platform' => $request->get_param( 'platform' ),
+ 'device_uuid' => $request->get_param( 'device_uuid' ),
+ 'origin' => $request->get_param( 'origin' ),
+ 'device_locale' => $request->get_param( 'device_locale' ),
+ 'metadata' => $request->get_param( 'metadata' ),
);
$data_store = wc_get_container()->get( PushTokensDataStore::class );
@@ -118,6 +118,8 @@ class PushTokenRestController extends RestApiControllerBase {
if ( $push_token ) {
$push_token->set_token( $data['token'] );
$push_token->set_device_uuid( $data['device_uuid'] );
+ $push_token->set_device_locale( $data['device_locale'] );
+ $push_token->set_metadata( $data['metadata'] );
$data_store->update( $push_token );
} else {
$push_token = $data_store->create( $data );
@@ -296,7 +298,7 @@ class PushTokenRestController extends RestApiControllerBase {
*/
private function get_args( ?string $context = null ): array {
$args = array(
- 'id' => array(
+ 'id' => array(
'description' => __( 'Push Token ID', 'woocommerce' ),
'type' => 'integer',
'required' => true,
@@ -305,7 +307,7 @@ class PushTokenRestController extends RestApiControllerBase {
'sanitize_callback' => 'absint',
'validate_callback' => array( $this, 'validate_argument' ),
),
- 'origin' => array(
+ 'origin' => array(
'description' => __( 'Origin', 'woocommerce' ),
'type' => 'string',
'required' => true,
@@ -313,7 +315,7 @@ class PushTokenRestController extends RestApiControllerBase {
'enum' => PushToken::ORIGINS,
'validate_callback' => array( $this, 'validate_argument' ),
),
- 'device_uuid' => array(
+ 'device_uuid' => array(
'description' => __( 'Device UUID', 'woocommerce' ),
'default' => '',
'type' => 'string',
@@ -321,7 +323,15 @@ class PushTokenRestController extends RestApiControllerBase {
'validate_callback' => array( $this, 'validate_argument' ),
'sanitize_callback' => 'sanitize_text_field',
),
- 'platform' => array(
+ 'device_locale' => array(
+ 'description' => __( 'Device Locale', 'woocommerce' ),
+ 'type' => 'string',
+ 'required' => true,
+ 'context' => array( 'create' ),
+ 'validate_callback' => array( $this, 'validate_argument' ),
+ 'sanitize_callback' => 'sanitize_text_field',
+ ),
+ 'platform' => array(
'description' => __( 'Platform', 'woocommerce' ),
'type' => 'string',
'required' => true,
@@ -329,7 +339,7 @@ class PushTokenRestController extends RestApiControllerBase {
'enum' => PushToken::PLATFORMS,
'validate_callback' => array( $this, 'validate_argument' ),
),
- 'token' => array(
+ 'token' => array(
'description' => __( 'Push Token', 'woocommerce' ),
'type' => 'string',
'required' => true,
@@ -337,6 +347,13 @@ class PushTokenRestController extends RestApiControllerBase {
'validate_callback' => array( $this, 'validate_argument' ),
'sanitize_callback' => 'wp_unslash',
),
+ 'metadata' => array(
+ 'description' => __( 'Metadata', 'woocommerce' ),
+ 'type' => 'object',
+ 'context' => array( 'create' ),
+ 'validate_callback' => array( $this, 'validate_argument' ),
+ 'sanitize_callback' => 'wp_unslash',
+ ),
);
if ( $context ) {
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php b/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php
index 5b6e242d9a..85b38b4873 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/DataStores/PushTokensDataStore.php
@@ -28,6 +28,8 @@ class PushTokensDataStore {
'device_uuid',
'token',
'platform',
+ 'device_locale',
+ 'metadata',
);
/**
@@ -112,6 +114,13 @@ class PushTokensDataStore {
$push_token->set_platform( $meta['platform'] );
$push_token->set_origin( $meta['origin'] );
+ /**
+ * These meta items were added after the ability to store tokens, so may
+ * not be available for older tokens. Use sensible defaults.
+ */
+ $push_token->set_device_locale( $meta['device_locale'] ?? PushToken::DEFAULT_DEVICE_LOCALE );
+ $push_token->set_metadata( $meta['metadata'] ?? array() );
+
return $push_token;
}
@@ -272,12 +281,19 @@ class PushTokensDataStore {
) {
return new PushToken(
array(
- 'id' => $post_id,
- 'user_id' => $user_id,
- 'token' => $meta['token'],
- 'device_uuid' => $meta['device_uuid'] ?? null,
- 'platform' => $meta['platform'],
- 'origin' => $meta['origin'],
+ 'id' => $post_id,
+ 'user_id' => $user_id,
+ 'token' => $meta['token'],
+ 'device_uuid' => $meta['device_uuid'] ?? null,
+ 'platform' => $meta['platform'],
+ 'origin' => $meta['origin'],
+ /**
+ * These meta items were added after the ability to store
+ * tokens, so may not be available for older tokens. Use
+ * sensible defaults.
+ */
+ 'device_locale' => $meta['device_locale'] ?? PushToken::DEFAULT_DEVICE_LOCALE,
+ 'metadata' => $meta['metadata'] ?? array(),
)
);
}
@@ -288,23 +304,23 @@ class PushTokensDataStore {
/**
* Returns an associative array of post meta as key => value pairs for the
- * keys defined in SUPPORTED_META; missing keys return null.
+ * keys defined in SUPPORTED_META; missing keys return null. Use
+ * `update_meta_cache` with `get_post_meta` to allow reading the meta as
+ * single values which automatically unserialize when requires,
+ * rather than nested arrays that don't.
*
* @since 10.5.0
* @param int $id The push token ID.
* @return array
*/
private function build_meta_array_from_database( int $id ): array {
- $meta = (array) get_post_meta( $id );
- $meta_by_key = (array) array_combine( static::SUPPORTED_META, static::SUPPORTED_META );
+ $meta_by_key = array_fill_keys( static::SUPPORTED_META, null );
foreach ( static::SUPPORTED_META as $key ) {
- if ( ! isset( $meta[ $key ] ) ) {
- $meta_by_key[ $key ] = null;
- } elseif ( is_array( $meta[ $key ] ) ) {
- $meta_by_key[ $key ] = $meta[ $key ][0];
- } else {
- $meta_by_key[ $key ] = $meta[ $key ];
+ $meta = get_post_meta( $id, $key, true );
+
+ if ( '' !== $meta ) {
+ $meta_by_key[ $key ] = $meta;
}
}
@@ -322,11 +338,14 @@ class PushTokensDataStore {
private function build_meta_array_from_token( PushToken $push_token ) {
return array_filter(
array(
- 'platform' => $push_token->get_platform(),
- 'token' => $push_token->get_token(),
- 'device_uuid' => $push_token->get_device_uuid(),
- 'origin' => $push_token->get_origin(),
- )
+ 'platform' => $push_token->get_platform(),
+ 'token' => $push_token->get_token(),
+ 'device_uuid' => $push_token->get_device_uuid(),
+ 'origin' => $push_token->get_origin(),
+ 'device_locale' => $push_token->get_device_locale(),
+ 'metadata' => $push_token->get_metadata(),
+ ),
+ fn ( $value ) => null !== $value && '' !== $value
);
}
}
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Entities/PushToken.php b/plugins/woocommerce/src/Internal/PushNotifications/Entities/PushToken.php
index aaf2e0063f..5fe3d62035 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Entities/PushToken.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Entities/PushToken.php
@@ -20,6 +20,12 @@ class PushToken {
*/
const POST_TYPE = 'wc_push_token';
+ /**
+ * The locale to use for tokens when the locale is not set, e.g. for tokens
+ * created before `device_locale` was added.
+ */
+ const DEFAULT_DEVICE_LOCALE = 'en_US';
+
/**
* Platform identifier for Apple devices.
*/
@@ -122,6 +128,20 @@ class PushToken {
*/
private ?string $origin = null;
+ /**
+ * The locale of the device the token belongs to.
+ *
+ * @var string|null
+ */
+ private ?string $device_locale = null;
+
+ /**
+ * An array of metadata for the token.
+ *
+ * @var array|null
+ */
+ private ?array $metadata = null;
+
/**
* Creates a new PushToken instance with the given data.
*
@@ -154,6 +174,14 @@ class PushToken {
if ( array_key_exists( 'origin', $data ) ) {
$this->set_origin( (string) $data['origin'] );
}
+
+ if ( array_key_exists( 'device_locale', $data ) ) {
+ $this->set_device_locale( (string) $data['device_locale'] );
+ }
+
+ if ( array_key_exists( 'metadata', $data ) ) {
+ $this->set_metadata( (array) $data['metadata'] );
+ }
}
/**
@@ -240,6 +268,26 @@ class PushToken {
$this->device_uuid = $device_uuid ? $device_uuid : null;
}
+ /**
+ * Validates and sets the device locale.
+ *
+ * @param string $device_locale The locale of the device the token belongs to.
+ * @throws PushTokenInvalidDataException If device locale is not valid.
+ * @return void
+ *
+ * @since 10.6.0
+ */
+ public function set_device_locale( string $device_locale ): void {
+ $result = PushTokenValidator::validate( compact( 'device_locale' ), array( 'device_locale' ) );
+
+ if ( is_wp_error( $result ) ) {
+ // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+ throw new PushTokenInvalidDataException( $result->get_error_message() );
+ }
+
+ $this->device_locale = trim( $device_locale );
+ }
+
/**
* Validates and sets the platform.
*
@@ -280,6 +328,39 @@ class PushToken {
$this->origin = trim( $origin );
}
+ /**
+ * Validates and sets the metadata.
+ *
+ * @param array $metadata An array of metadata for the token, e.g. the app version, device OS etc.
+ * @throws PushTokenInvalidDataException If metadata is not valid.
+ * @return void
+ *
+ * @since 10.6.0
+ */
+ public function set_metadata( array $metadata ): void {
+ $result = PushTokenValidator::validate( compact( 'metadata' ), array( 'metadata' ) );
+
+ if ( is_wp_error( $result ) ) {
+ // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped
+ throw new PushTokenInvalidDataException( $result->get_error_message() );
+ }
+
+ if ( ! empty( $metadata ) ) {
+ $keys = array_map( 'sanitize_key', array_keys( $metadata ) );
+ $values = array_map( 'sanitize_text_field', array_values( $metadata ) );
+
+ /**
+ * Typehint for PHPStan, as it can't infer the $keys and $values are
+ * the same length therefore array_combine won't return false.
+ *
+ * @var array<string, string> $metadata
+ */
+ $metadata = array_combine( $keys, $values );
+ }
+
+ $this->metadata = $metadata;
+ }
+
/**
* Gets the ID.
*
@@ -346,6 +427,28 @@ class PushToken {
return $this->origin;
}
+ /**
+ * Gets the device locale.
+ *
+ * @return string|null
+ *
+ * @since 10.6.0
+ */
+ public function get_device_locale(): ?string {
+ return $this->device_locale;
+ }
+
+ /**
+ * Gets the metadata.
+ *
+ * @return array|null
+ *
+ * @since 10.6.0
+ */
+ public function get_metadata(): ?array {
+ return $this->metadata;
+ }
+
/**
* Determines whether this token can be created.
*
@@ -402,6 +505,7 @@ class PushToken {
&& $this->get_token()
&& $this->get_platform()
&& $this->get_origin()
+ && $this->get_device_locale()
&& (
$this->get_device_uuid()
|| $this->get_platform() === self::PLATFORM_BROWSER
diff --git a/plugins/woocommerce/src/Internal/PushNotifications/Validators/PushTokenValidator.php b/plugins/woocommerce/src/Internal/PushNotifications/Validators/PushTokenValidator.php
index da3b27d90c..1bee511fa0 100644
--- a/plugins/woocommerce/src/Internal/PushNotifications/Validators/PushTokenValidator.php
+++ b/plugins/woocommerce/src/Internal/PushNotifications/Validators/PushTokenValidator.php
@@ -12,6 +12,8 @@ defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Internal\PushNotifications\Entities\PushToken;
use WP_Error;
+// phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
+
/**
* Validator class for push tokens.
*
@@ -23,8 +25,10 @@ class PushTokenValidator {
'user_id',
'origin',
'device_uuid',
+ 'device_locale',
'platform',
'token',
+ 'metadata',
);
/**
@@ -34,6 +38,14 @@ class PushTokenValidator {
*/
const ERROR_CODE = 'woocommerce_invalid_data';
+ /**
+ * Validates device locale format:
+ * - language code (2–3 lowercase letters)
+ * - underscore
+ * - region code (2 uppercase letters).
+ */
+ const DEVICE_LOCALE_FORMAT = '/^(?<language>[a-z]{2,3})_(?<region>[A-Z]{2})$/';
+
/**
* The regex to use when validating device UUID format.
*
@@ -107,17 +119,11 @@ class PushTokenValidator {
*
* @since 10.6.0
*
- * @param mixed $value The value to validate.
- * @param array $context An array of other values included as context for the validation.
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
* @return bool|WP_Error
*/
- private static function validate_id( $value, array $context ) {
- /**
- * Context is unused in this specific method, but required by the
- * validate() dispatch signature.
- */
- unset( $context );
-
+ private static function validate_id( $value, ?array $context = array() ) {
if ( is_null( $value ) ) {
return new WP_Error( self::ERROR_CODE, 'ID is required.' );
}
@@ -138,17 +144,11 @@ class PushTokenValidator {
*
* @since 10.6.0
*
- * @param mixed $value The value to validate.
- * @param array $context An array of other values included as context for the validation.
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
* @return bool|WP_Error
*/
- private static function validate_user_id( $value, array $context ) {
- /**
- * Context is unused in this specific method, but required by the
- * validate() dispatch signature.
- */
- unset( $context );
-
+ private static function validate_user_id( $value, ?array $context = array() ) {
if ( is_null( $value ) ) {
return new WP_Error( self::ERROR_CODE, 'User ID is required.' );
}
@@ -169,17 +169,11 @@ class PushTokenValidator {
*
* @since 10.6.0
*
- * @param mixed $value The value to validate.
- * @param array $context An array of other values included as context for the validation.
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
* @return bool|WP_Error
*/
- private static function validate_origin( $value, array $context ) {
- /**
- * Context is unused in this specific method, but required by the
- * validate() dispatch signature.
- */
- unset( $context );
-
+ private static function validate_origin( $value, ?array $context = array() ) {
if ( is_null( $value ) ) {
return new WP_Error( self::ERROR_CODE, 'Origin is required.' );
}
@@ -209,11 +203,11 @@ class PushTokenValidator {
*
* @since 10.6.0
*
- * @param mixed $value The value to validate.
- * @param array $context An array of other values included as context for the validation.
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
* @return bool|WP_Error
*/
- private static function validate_device_uuid( $value, array $context ) {
+ private static function validate_device_uuid( $value, ?array $context = array() ) {
/**
* We may or may not have platform; if we don't have it, we can skip the
* platform-specific checks and allow the platform validation to trigger
@@ -266,22 +260,47 @@ class PushTokenValidator {
return true;
}
+ /**
+ * Validates device locale.
+ *
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
+ * @return bool|WP_Error
+ *
+ * @since 10.6.0
+ */
+ private static function validate_device_locale( $value, ?array $context = array() ) {
+ if ( ! isset( $value ) ) {
+ return new WP_Error( self::ERROR_CODE, 'Device locale is required.' );
+ }
+
+ if ( ! is_string( $value ) ) {
+ return new WP_Error( self::ERROR_CODE, 'Device locale must be a string.' );
+ }
+
+ $value = trim( $value );
+
+ if ( '' === $value ) {
+ return new WP_Error( self::ERROR_CODE, 'Device locale cannot be empty.' );
+ }
+
+ if ( ! preg_match( self::DEVICE_LOCALE_FORMAT, $value ) ) {
+ return new WP_Error( self::ERROR_CODE, 'Device locale is an invalid format.' );
+ }
+
+ return true;
+ }
+
/**
* Validates platform.
*
* @since 10.6.0
*
- * @param mixed $value The value to validate.
- * @param array $context An array of other values included as context for the validation.
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
* @return bool|WP_Error
*/
- private static function validate_platform( $value, array $context ) {
- /**
- * Context is unused in this specific method, but required by the
- * validate() dispatch signature.
- */
- unset( $context );
-
+ private static function validate_platform( $value, ?array $context = array() ) {
if ( is_null( $value ) ) {
return new WP_Error( self::ERROR_CODE, 'Platform is required.' );
}
@@ -311,11 +330,11 @@ class PushTokenValidator {
*
* @since 10.6.0
*
- * @param mixed $value The value to validate.
- * @param array $context An array of other values included as context for the validation.
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
* @return bool|WP_Error
*/
- private static function validate_token( $value, array $context ) {
+ private static function validate_token( $value, ?array $context = array() ) {
if ( is_null( $value ) ) {
return new WP_Error( self::ERROR_CODE, 'Token is required.' );
}
@@ -378,4 +397,33 @@ class PushTokenValidator {
return true;
}
+
+ /**
+ * Validates metadata.
+ *
+ * @param mixed $value The value to validate.
+ * @param array|null $context An array of other values included as context for the validation.
+ * @return bool|WP_Error
+ *
+ * @since 10.6.0
+ */
+ private static function validate_metadata( $value, ?array $context = array() ) {
+ if ( ! isset( $value ) ) {
+ return new WP_Error( self::ERROR_CODE, 'Metadata is required.' );
+ }
+
+ if ( ! is_array( $value ) ) {
+ return new WP_Error( self::ERROR_CODE, 'Metadata must be an array.' );
+ }
+
+ foreach ( $value as $key => $item ) {
+ if ( ! is_scalar( $item ) ) {
+ return new WP_Error( self::ERROR_CODE, 'Metadata items must be scalar values.' );
+ }
+ }
+
+ return true;
+ }
}
+
+// phpcs:enable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Controllers/PushTokenRestControllerTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Controllers/PushTokenRestControllerTest.php
index 865e566e03..5f823cc835 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Controllers/PushTokenRestControllerTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Controllers/PushTokenRestControllerTest.php
@@ -117,6 +117,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', $device_uuid );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -154,6 +156,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_ANDROID );
$request->set_param( 'device_uuid', $device_uuid );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_ANDROID );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -193,6 +197,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'device-1' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -208,6 +214,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'device-2' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -249,6 +257,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', $device_uuid );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -264,6 +274,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', $device_uuid );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -286,6 +298,66 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
);
}
+ /**
+ * @testdox Test it updates a legacy token that was created without
+ * device_locale and metadata.
+ */
+ public function test_it_updates_legacy_token_without_device_locale_and_metadata() {
+ wp_set_current_user( $this->user_id );
+
+ $this->mock_jetpack_connection_manager_is_connected( true );
+
+ $token_value = str_repeat( 'a', 64 );
+ $device_uuid = 'legacy-device-uuid';
+
+ /**
+ * Insert a legacy token directly into the database without
+ * device_locale and metadata meta.
+ */
+ $post_id = wp_insert_post(
+ array(
+ 'post_author' => $this->user_id,
+ 'post_type' => PushToken::POST_TYPE,
+ 'post_status' => 'private',
+ 'meta_input' => array(
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'token' => $token_value,
+ 'device_uuid' => $device_uuid,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ ),
+ )
+ );
+
+ /**
+ * Re-register the same token via the REST API, which should find the
+ * legacy token and update it.
+ */
+ $request = new WP_REST_Request( 'POST', '/wc-push-notifications/push-tokens' );
+ $request->set_param( 'token', $token_value );
+ $request->set_param( 'platform', PushToken::PLATFORM_APPLE );
+ $request->set_param( 'device_uuid', $device_uuid );
+ $request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'fr_FR' );
+ $request->set_param( 'metadata', array( 'app_version' => '2.0' ) );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( WP_Http::CREATED, $response->get_status() );
+
+ $data = $response->get_data();
+
+ $this->assertEquals( $post_id, $data['id'] );
+
+ /**
+ * Verify the legacy token was updated with the new fields.
+ */
+ $device_locale = get_post_meta( $post_id, 'device_locale', true );
+ $metadata = get_post_meta( $post_id, 'metadata', true );
+
+ $this->assertEquals( 'fr_FR', $device_locale );
+ $this->assertEquals( array( 'app_version' => '2.0' ), $metadata );
+ }
+
/**
* @testdox Test it cannot create a push token without authentication.
*/
@@ -295,6 +367,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -318,6 +392,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -338,6 +414,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -363,6 +441,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid-nonhex' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -386,6 +466,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid-short' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -410,6 +492,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_ANDROID );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_ANDROID );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -434,6 +518,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_ANDROID );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_ANDROID );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -457,6 +543,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'token', 'not-valid-json' );
$request->set_param( 'platform', PushToken::PLATFORM_BROWSER );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -487,6 +575,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'token', $token );
$request->set_param( 'platform', PushToken::PLATFORM_BROWSER );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -520,6 +610,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'token', $token );
$request->set_param( 'platform', PushToken::PLATFORM_BROWSER );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -543,6 +635,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -566,6 +660,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'token', str_repeat( 'a', 64 ) );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -589,6 +685,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'token', str_repeat( 'a', 64 ) );
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -613,6 +711,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', 'windows' );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -636,6 +736,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'token', str_repeat( 'a', 64 ) );
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid' );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -659,6 +761,115 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'test-device-uuid' );
$request->set_param( 'origin', 'development' );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( WP_Http::BAD_REQUEST, $response->get_status() );
+
+ $data = $response->get_data();
+
+ $this->assertEquals( 'rest_invalid_param', $data['code'] );
+ }
+
+ /**
+ * @testdox Test it cannot create a push token without required device_locale
+ * parameter.
+ */
+ public function test_it_cannot_create_push_token_with_a_missing_device_locale() {
+ wp_set_current_user( $this->user_id );
+
+ $this->mock_jetpack_connection_manager_is_connected( true );
+
+ $request = new WP_REST_Request( 'POST', '/wc-push-notifications/push-tokens' );
+ $request->set_param( 'token', str_repeat( 'a', 64 ) );
+ $request->set_param( 'platform', PushToken::PLATFORM_APPLE );
+ $request->set_param( 'device_uuid', 'test-device-uuid' );
+ $request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'metadata', array() );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( WP_Http::BAD_REQUEST, $response->get_status() );
+
+ $data = $response->get_data();
+
+ $this->assertEquals( 'rest_missing_callback_param', $data['code'] );
+ }
+
+ /**
+ * @testdox Test it cannot create a push token with invalid device_locale
+ * format.
+ */
+ public function test_it_cannot_create_push_token_with_invalid_device_locale_format() {
+ wp_set_current_user( $this->user_id );
+
+ $this->mock_jetpack_connection_manager_is_connected( true );
+
+ $request = new WP_REST_Request( 'POST', '/wc-push-notifications/push-tokens' );
+ $request->set_param( 'token', str_repeat( 'a', 64 ) );
+ $request->set_param( 'platform', PushToken::PLATFORM_APPLE );
+ $request->set_param( 'device_uuid', 'test-device-uuid' );
+ $request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'invalid-locale' );
+ $request->set_param( 'metadata', array() );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( WP_Http::BAD_REQUEST, $response->get_status() );
+
+ $data = $response->get_data();
+
+ $this->assertEquals( 'rest_invalid_param', $data['code'] );
+ $this->assertStringContainsString( 'Invalid parameter(s): device_locale', $data['message'] );
+ }
+
+ /**
+ * @testdox Test it can create a push token without required metadata
+ * parameter.
+ */
+ public function test_it_can_create_push_token_with_a_missing_metadata() {
+ wp_set_current_user( $this->user_id );
+
+ $this->mock_jetpack_connection_manager_is_connected( true );
+
+ $token_value = str_repeat( 'a', 64 );
+ $device_uuid = 'test-device-uuid';
+
+ $request = new WP_REST_Request( 'POST', '/wc-push-notifications/push-tokens' );
+ $request->set_param( 'token', $token_value );
+ $request->set_param( 'platform', PushToken::PLATFORM_APPLE );
+ $request->set_param( 'device_uuid', $device_uuid );
+ $request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+
+ $response = $this->server->dispatch( $request );
+
+ $this->assertEquals( WP_Http::CREATED, $response->get_status() );
+
+ $data = $response->get_data();
+
+ $this->assertArrayHasKey( 'id', $data );
+ $this->assertIsInt( $data['id'] );
+ $this->assertGreaterThan( 0, $data['id'] );
+ }
+
+ /**
+ * @testdox Test it cannot create a push token with non-array metadata.
+ */
+ public function test_it_cannot_create_push_token_with_non_array_metadata() {
+ wp_set_current_user( $this->user_id );
+
+ $this->mock_jetpack_connection_manager_is_connected( true );
+
+ $request = new WP_REST_Request( 'POST', '/wc-push-notifications/push-tokens' );
+ $request->set_param( 'token', str_repeat( 'a', 64 ) );
+ $request->set_param( 'platform', PushToken::PLATFORM_APPLE );
+ $request->set_param( 'device_uuid', 'test-device-uuid' );
+ $request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', 'not an array' );
$response = $this->server->dispatch( $request );
@@ -667,6 +878,7 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$data = $response->get_data();
$this->assertEquals( 'rest_invalid_param', $data['code'] );
+ $this->assertStringContainsString( 'Invalid parameter(s): metadata', $data['message'] );
}
/**
@@ -681,11 +893,13 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
* Create a token first.
*/
$data = array(
- 'user_id' => $this->user_id,
- 'token' => str_repeat( 'a', 64 ),
- 'platform' => PushToken::PLATFORM_APPLE,
- 'device_uuid' => 'device-to-delete',
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => $this->user_id,
+ 'token' => str_repeat( 'a', 64 ),
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_uuid' => 'device-to-delete',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
);
$data_store = wc_get_container()->get( PushTokensDataStore::class );
@@ -740,11 +954,13 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
* Create a token for another shop manager.
*/
$data = array(
- 'user_id' => $this->other_shop_manager_id,
- 'token' => str_repeat( 'a', 64 ),
- 'platform' => PushToken::PLATFORM_APPLE,
- 'device_uuid' => 'device-other-user',
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => $this->other_shop_manager_id,
+ 'token' => str_repeat( 'a', 64 ),
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_uuid' => 'device-other-user',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
);
$data_store = wc_get_container()->get( PushTokensDataStore::class );
@@ -794,11 +1010,13 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
*/
public function test_it_returns_500_when_wp_delete_post_fails() {
$data = array(
- 'user_id' => $this->user_id,
- 'token' => str_repeat( 'a', 64 ),
- 'platform' => PushToken::PLATFORM_APPLE,
- 'device_uuid' => 'device-delete-fail',
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => $this->user_id,
+ 'token' => str_repeat( 'a', 64 ),
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_uuid' => 'device-delete-fail',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
);
$data_store = wc_get_container()->get( PushTokensDataStore::class );
@@ -907,6 +1125,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', str_repeat( 'a', 256 ) );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -932,6 +1152,8 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$request->set_param( 'platform', PushToken::PLATFORM_APPLE );
$request->set_param( 'device_uuid', 'invalid device uuid with spaces' );
$request->set_param( 'origin', PushToken::ORIGIN_WOOCOMMERCE_IOS );
+ $request->set_param( 'device_locale', 'en_US' );
+ $request->set_param( 'metadata', array( 'app_version' => '1.0' ) );
$response = $this->server->dispatch( $request );
@@ -1191,5 +1413,9 @@ class PushTokenRestControllerTest extends WC_REST_Unit_Test_Case {
$this->assertEquals( $platform, $meta['platform'] );
$this->assertEquals( $device_uuid, $meta['device_uuid'] );
$this->assertEquals( $origin, $meta['origin'] );
+ $this->assertArrayHasKey( 'device_locale', $meta );
+ $this->assertEquals( 'en_US', $meta['device_locale'] );
+ $this->assertArrayHasKey( 'metadata', $meta );
+ $this->assertEquals( array( 'app_version' => '1.0' ), maybe_unserialize( $meta['metadata'] ) );
}
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php
index 31af803d1d..18eabd82d4 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/DataStores/PushTokensDataStoreTest.php
@@ -48,11 +48,13 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
$data_store = new PushTokensDataStore();
$data = array(
- 'user_id' => 1,
- 'token' => 'test_token_12345',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'device_uuid' => 'device-uuid-123',
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'test_token_12345',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_uuid' => 'device-uuid-123',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
);
$push_token = $data_store->create( $data );
@@ -79,6 +81,8 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
$this->assertEquals( $original_push_token->get_token(), $read_push_token->get_token() );
$this->assertEquals( $original_push_token->get_device_uuid(), $read_push_token->get_device_uuid() );
$this->assertEquals( $original_push_token->get_origin(), $read_push_token->get_origin() );
+ $this->assertEquals( $original_push_token->get_device_locale(), $read_push_token->get_device_locale() );
+ $this->assertEquals( $original_push_token->get_metadata(), $read_push_token->get_metadata() );
}
/**
@@ -421,11 +425,13 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
*/
$browser_token_1 = $data_store->create(
array(
- 'user_id' => 1,
- 'token' => 'browser_token_1_' . wp_rand(),
- 'platform' => PushToken::PLATFORM_BROWSER,
- 'device_uuid' => null,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'browser_token_1_' . wp_rand(),
+ 'platform' => PushToken::PLATFORM_BROWSER,
+ 'device_uuid' => null,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -434,11 +440,13 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
*/
$browser_token_2 = $data_store->create(
array(
- 'user_id' => 1,
- 'token' => 'browser_token_2_' . wp_rand(),
- 'platform' => PushToken::PLATFORM_BROWSER,
- 'device_uuid' => null,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'browser_token_2_' . wp_rand(),
+ 'platform' => PushToken::PLATFORM_BROWSER,
+ 'device_uuid' => null,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -571,10 +579,12 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
$data_store = new PushTokensDataStore();
$data = array(
- 'user_id' => 1,
- 'token' => '{"endpoint":"https://example.com/push","keys":{"auth":"test","p256dh":"test"}}',
- 'platform' => PushToken::PLATFORM_BROWSER,
- 'origin' => PushToken::ORIGIN_BROWSER,
+ 'user_id' => 1,
+ 'token' => '{"endpoint":"https://example.com/push","keys":{"auth":"test","p256dh":"test"}}',
+ 'platform' => PushToken::PLATFORM_BROWSER,
+ 'origin' => PushToken::ORIGIN_BROWSER,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
);
$push_token = $data_store->create( $data );
@@ -591,6 +601,108 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
$this->assertNull( $read_token->get_device_uuid() );
}
+ /**
+ * @testdox Tests that a legacy token without device_locale and metadata can
+ * be read with sensible defaults applied.
+ */
+ public function test_it_can_read_legacy_token_without_device_locale_and_metadata() {
+ $data_store = new PushTokensDataStore();
+
+ $post_id = wp_insert_post(
+ array(
+ 'post_author' => 1,
+ 'post_type' => PushToken::POST_TYPE,
+ 'post_status' => 'private',
+ 'meta_input' => array(
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'token' => 'legacy_token_value',
+ 'device_uuid' => 'legacy-device-uuid',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ ),
+ )
+ );
+
+ $push_token = $data_store->read( $post_id );
+
+ $this->assertEquals( $post_id, $push_token->get_id() );
+ $this->assertEquals( 'legacy_token_value', $push_token->get_token() );
+ $this->assertEquals( PushToken::DEFAULT_DEVICE_LOCALE, $push_token->get_device_locale() );
+ $this->assertEquals( array(), $push_token->get_metadata() );
+ }
+
+ /**
+ * @testdox Tests that a legacy token without device_locale and metadata can
+ * be found by get_by_token_or_device_id with defaults applied.
+ */
+ public function test_it_can_find_legacy_token_by_token_or_device_id_with_defaults() {
+ $data_store = new PushTokensDataStore();
+
+ $post_id = wp_insert_post(
+ array(
+ 'post_author' => 1,
+ 'post_type' => PushToken::POST_TYPE,
+ 'post_status' => 'private',
+ 'meta_input' => array(
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'token' => 'legacy_token_value',
+ 'device_uuid' => 'legacy-device-uuid',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ ),
+ )
+ );
+
+ $found_token = $data_store->get_by_token_or_device_id(
+ array(
+ 'user_id' => 1,
+ 'token' => 'legacy_token_value',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_uuid' => 'different-device',
+ )
+ );
+
+ $this->assertNotNull( $found_token );
+ $this->assertEquals( $post_id, $found_token->get_id() );
+ $this->assertEquals( PushToken::DEFAULT_DEVICE_LOCALE, $found_token->get_device_locale() );
+ $this->assertEquals( array(), $found_token->get_metadata() );
+ }
+
+ /**
+ * @testdox Tests that a legacy token can be updated with new device_locale
+ * and metadata values.
+ */
+ public function test_it_can_update_legacy_token_with_new_locale_and_metadata() {
+ $data_store = new PushTokensDataStore();
+
+ $post_id = wp_insert_post(
+ array(
+ 'post_author' => 1,
+ 'post_type' => PushToken::POST_TYPE,
+ 'post_status' => 'private',
+ 'meta_input' => array(
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'token' => 'legacy_token_value',
+ 'device_uuid' => 'legacy-device-uuid',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ ),
+ )
+ );
+
+ $push_token = $data_store->read( $post_id );
+
+ $this->assertEquals( PushToken::DEFAULT_DEVICE_LOCALE, $push_token->get_device_locale() );
+ $this->assertEquals( array(), $push_token->get_metadata() );
+
+ $push_token->set_device_locale( 'fr_FR' );
+ $push_token->set_metadata( array( 'app_version' => '2.0' ) );
+ $data_store->update( $push_token );
+
+ $updated_token = $data_store->read( $post_id );
+
+ $this->assertEquals( 'fr_FR', $updated_token->get_device_locale() );
+ $this->assertEquals( array( 'app_version' => '2.0' ), $updated_token->get_metadata() );
+ }
+
/**
* Creates a test push token and saves it to the database.
*
@@ -600,11 +712,13 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
$data_store = new PushTokensDataStore();
$data = array(
- 'user_id' => 1,
- 'token' => 'test_token_' . wp_rand(),
- 'platform' => PushToken::PLATFORM_APPLE,
- 'device_uuid' => 'test-device-uuid-' . wp_rand(),
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'test_token_' . wp_rand(),
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_uuid' => 'test-device-uuid-' . wp_rand(),
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
);
return $data_store->create( $data );
@@ -622,14 +736,18 @@ class PushTokensDataStoreTest extends WC_Unit_Test_Case {
$this->assertEquals( PushToken::POST_TYPE, $post->post_type );
$this->assertEquals( $push_token->get_user_id(), $post->post_author );
- $platform = get_post_meta( $push_token->get_id(), 'platform', true );
- $token = get_post_meta( $push_token->get_id(), 'token', true );
- $device_uuid = get_post_meta( $push_token->get_id(), 'device_uuid', true );
- $origin = get_post_meta( $push_token->get_id(), 'origin', true );
+ $platform = get_post_meta( $push_token->get_id(), 'platform', true );
+ $token = get_post_meta( $push_token->get_id(), 'token', true );
+ $device_uuid = get_post_meta( $push_token->get_id(), 'device_uuid', true );
+ $origin = get_post_meta( $push_token->get_id(), 'origin', true );
+ $device_locale = get_post_meta( $push_token->get_id(), 'device_locale', true );
+ $metadata = get_post_meta( $push_token->get_id(), 'metadata', true );
$this->assertEquals( $push_token->get_platform(), $platform );
$this->assertEquals( $push_token->get_token(), $token );
$this->assertEquals( $push_token->get_device_uuid(), $device_uuid );
$this->assertEquals( $push_token->get_origin(), $origin );
+ $this->assertEquals( $push_token->get_device_locale(), $device_locale );
+ $this->assertEquals( $push_token->get_metadata(), $metadata );
}
}
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Entities/PushTokenTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Entities/PushTokenTest.php
index 624f01800f..dd0af6e809 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Entities/PushTokenTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Entities/PushTokenTest.php
@@ -71,11 +71,13 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_can_be_created_when_all_fields_are_set_except_id() {
$push_token = new PushToken(
array(
- 'user_id' => 1,
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -88,12 +90,14 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_created_when_id_is_set() {
$push_token = new PushToken(
array(
- 'id' => 1,
- 'user_id' => 1,
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 1,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -106,10 +110,12 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_created_when_user_id_is_missing() {
$push_token = new PushToken(
array(
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -122,10 +128,12 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_created_when_platform_is_missing() {
$push_token = new PushToken(
array(
- 'user_id' => 1,
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -138,10 +146,12 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_created_when_token_is_missing() {
$push_token = new PushToken(
array(
- 'user_id' => 1,
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -154,10 +164,12 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_created_when_device_uuid_is_missing() {
$push_token = new PushToken(
array(
- 'user_id' => 1,
- 'token' => 'test_token',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -171,12 +183,14 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_can_be_updated_when_all_fields_are_set() {
$push_token = new PushToken(
array(
- 'id' => 1,
- 'user_id' => 1,
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 1,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -189,11 +203,13 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_updated_when_id_is_not_set() {
$push_token = new PushToken(
array(
- 'user_id' => 1,
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -206,11 +222,13 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_updated_when_user_id_is_not_set() {
$push_token = new PushToken(
array(
- 'id' => 1,
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -223,11 +241,13 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_updated_when_platform_is_not_set() {
$push_token = new PushToken(
array(
- 'id' => 1,
- 'user_id' => 1,
- 'token' => 'test_token',
- 'device_uuid' => 'test-device-uuid',
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 1,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -240,11 +260,13 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_updated_when_device_uuid_is_not_set() {
$push_token = new PushToken(
array(
- 'id' => 1,
- 'user_id' => 1,
- 'token' => 'test_token',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 1,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -257,11 +279,13 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_cannot_be_updated_when_token_is_not_set() {
$push_token = new PushToken(
array(
- 'id' => 1,
- 'user_id' => 1,
- 'device_uuid' => 'test-device-uuid',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 1,
+ 'user_id' => 1,
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -334,10 +358,12 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_can_be_created_for_browser_without_device_uuid() {
$push_token = new PushToken(
array(
- 'user_id' => 1,
- 'token' => 'test_token',
- 'platform' => PushToken::PLATFORM_BROWSER,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'platform' => PushToken::PLATFORM_BROWSER,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -351,11 +377,13 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_it_can_be_updated_for_browser_without_device_uuid() {
$push_token = new PushToken(
array(
- 'id' => 1,
- 'user_id' => 1,
- 'token' => 'test_token',
- 'platform' => PushToken::PLATFORM_BROWSER,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 1,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'platform' => PushToken::PLATFORM_BROWSER,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -366,12 +394,51 @@ class PushTokenTest extends WC_Unit_Test_Case {
* @testdox Tests can_be_created returns false when origin is missing.
*/
public function test_it_cannot_be_created_when_origin_is_missing() {
+ $push_token = new PushToken(
+ array(
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
+ )
+ );
+
+ $this->assertFalse( $push_token->can_be_created() );
+ }
+
+ /**
+ * @testdox Tests can_be_updated returns false when origin is missing.
+ */
+ public function test_it_cannot_be_updated_when_origin_is_missing() {
+ $push_token = new PushToken(
+ array(
+ 'id' => 1,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
+ )
+ );
+
+ $this->assertFalse( $push_token->can_be_updated() );
+ }
+
+ /**
+ * @testdox Tests can_be_created returns false when device_locale is missing.
+ */
+ public function test_it_cannot_be_created_when_device_locale_is_missing() {
$push_token = new PushToken(
array(
'user_id' => 1,
'token' => 'test_token',
'device_uuid' => 'test-device-uuid',
'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -379,9 +446,9 @@ class PushTokenTest extends WC_Unit_Test_Case {
}
/**
- * @testdox Tests can_be_updated returns false when origin is missing.
+ * @testdox Tests can_be_updated returns false when device_locale is missing.
*/
- public function test_it_cannot_be_updated_when_origin_is_missing() {
+ public function test_it_cannot_be_updated_when_device_locale_is_missing() {
$push_token = new PushToken(
array(
'id' => 1,
@@ -389,12 +456,51 @@ class PushTokenTest extends WC_Unit_Test_Case {
'token' => 'test_token',
'device_uuid' => 'test-device-uuid',
'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
$this->assertFalse( $push_token->can_be_updated() );
}
+ /**
+ * @testdox Tests can_be_created returns true when metadata is missing.
+ */
+ public function test_it_can_be_created_when_metadata_is_missing() {
+ $push_token = new PushToken(
+ array(
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ )
+ );
+
+ $this->assertTrue( $push_token->can_be_created() );
+ }
+
+ /**
+ * @testdox Tests can_be_updated returns true when metadata is missing.
+ */
+ public function test_it_can_be_updated_when_metadata_is_missing() {
+ $push_token = new PushToken(
+ array(
+ 'id' => 1,
+ 'user_id' => 1,
+ 'token' => 'test_token',
+ 'device_uuid' => 'test-device-uuid',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ )
+ );
+
+ $this->assertTrue( $push_token->can_be_updated() );
+ }
+
/**
* @testdox Tests set_platform throws exception with invalid platform.
*/
@@ -465,6 +571,54 @@ class PushTokenTest extends WC_Unit_Test_Case {
$push_token->set_user_id( -1 );
}
+ /**
+ * @testdox Tests set_device_locale throws exception with empty string.
+ */
+ public function test_it_throws_exception_when_setting_empty_device_locale() {
+ $push_token = new PushToken();
+
+ $this->expectException( PushTokenInvalidDataException::class );
+ $this->expectExceptionMessage( 'Device locale cannot be empty.' );
+
+ $push_token->set_device_locale( '' );
+ }
+
+ /**
+ * @testdox Tests set_device_locale throws exception with invalid format.
+ */
+ public function test_it_throws_exception_when_setting_invalid_device_locale_format() {
+ $push_token = new PushToken();
+
+ $this->expectException( PushTokenInvalidDataException::class );
+ $this->expectExceptionMessage( 'Device locale is an invalid format.' );
+
+ $push_token->set_device_locale( 'invalid' );
+ }
+
+ /**
+ * @testdox Tests set_device_locale throws exception with lowercase region.
+ */
+ public function test_it_throws_exception_when_setting_device_locale_with_lowercase_region() {
+ $push_token = new PushToken();
+
+ $this->expectException( PushTokenInvalidDataException::class );
+ $this->expectExceptionMessage( 'Device locale is an invalid format.' );
+
+ $push_token->set_device_locale( 'en_gb' );
+ }
+
+ /**
+ * @testdox Tests set_metadata throws exception with non-scalar values.
+ */
+ public function test_it_throws_exception_when_setting_non_scalar_metadata() {
+ $push_token = new PushToken();
+
+ $this->expectException( PushTokenInvalidDataException::class );
+ $this->expectExceptionMessage( 'Metadata items must be scalar values.' );
+
+ $push_token->set_metadata( array( 'nested' => array( 'a' => 'b' ) ) );
+ }
+
/**
* @testdox Tests set_token throws exception with empty string.
*/
@@ -550,12 +704,14 @@ class PushTokenTest extends WC_Unit_Test_Case {
public function test_constructor_creates_token_with_all_properties() {
$push_token = new PushToken(
array(
- 'id' => 123,
- 'user_id' => 456,
- 'token' => 'test_token_value',
- 'device_uuid' => 'device-uuid-123',
- 'platform' => PushToken::PLATFORM_APPLE,
- 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'id' => 123,
+ 'user_id' => 456,
+ 'token' => 'test_token_value',
+ 'device_uuid' => 'device-uuid-123',
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'origin' => PushToken::ORIGIN_WOOCOMMERCE_IOS,
+ 'device_locale' => 'en_US',
+ 'metadata' => array( 'app_version' => '1.0' ),
)
);
@@ -565,6 +721,8 @@ class PushTokenTest extends WC_Unit_Test_Case {
$this->assertSame( 'device-uuid-123', $push_token->get_device_uuid() );
$this->assertSame( PushToken::PLATFORM_APPLE, $push_token->get_platform() );
$this->assertSame( PushToken::ORIGIN_WOOCOMMERCE_IOS, $push_token->get_origin() );
+ $this->assertSame( 'en_US', $push_token->get_device_locale() );
+ $this->assertSame( array( 'app_version' => '1.0' ), $push_token->get_metadata() );
}
/**
diff --git a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Validators/PushTokenValidatorTest.php b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Validators/PushTokenValidatorTest.php
index aa96b12a2b..07f3917cca 100644
--- a/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Validators/PushTokenValidatorTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/PushNotifications/Validators/PushTokenValidatorTest.php
@@ -20,12 +20,14 @@ class PushTokenValidatorTest extends WC_Unit_Test_Case {
public function test_it_validates_all_keys(): void {
$result = PushTokenValidator::validate(
array(
- 'id' => 1,
- 'user_id' => 42,
- 'origin' => PushToken::ORIGINS[0],
- 'platform' => PushToken::PLATFORM_APPLE,
- 'device_uuid' => 'valid-uuid-123',
- 'token' => null,
+ 'id' => 1,
+ 'user_id' => 42,
+ 'origin' => PushToken::ORIGINS[0],
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_uuid' => 'valid-uuid-123',
+ 'device_locale' => 'en_US',
+ 'token' => null,
+ 'metadata' => array(),
)
);
@@ -39,12 +41,14 @@ class PushTokenValidatorTest extends WC_Unit_Test_Case {
public function test_it_validates_all_keys_with_valid_data(): void {
$result = PushTokenValidator::validate(
array(
- 'id' => 1,
- 'user_id' => 42,
- 'origin' => PushToken::ORIGINS[0],
- 'platform' => PushToken::PLATFORM_APPLE,
- 'device_uuid' => 'valid-uuid-123',
- 'token' => str_repeat( 'a', 64 ),
+ 'id' => 1,
+ 'user_id' => 42,
+ 'origin' => PushToken::ORIGINS[0],
+ 'platform' => PushToken::PLATFORM_APPLE,
+ 'device_uuid' => 'valid-uuid-123',
+ 'device_locale' => 'en_US',
+ 'token' => str_repeat( 'a', 64 ),
+ 'metadata' => array(),
)
);
@@ -478,6 +482,74 @@ class PushTokenValidatorTest extends WC_Unit_Test_Case {
);
}
+ /**
+ * @testdox Should return true for valid locale formats.
+ * @dataProvider valid_locales_provider
+ * @param string $locale The locale to test.
+ */
+ public function test_validate_accepts_valid_device_locale_formats( string $locale ): void {
+ $this->assertTrue(
+ PushTokenValidator::validate(
+ array( 'device_locale' => $locale ),
+ array( 'device_locale' )
+ )
+ );
+ }
+
+ /**
+ * @testdox Should return WP_Error when device locale is missing.
+ */
+ public function test_validate_rejects_missing_device_locale(): void {
+ $result = PushTokenValidator::validate(
+ array(),
+ array( 'device_locale' )
+ );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertSame( 'Device locale is required.', $result->get_error_message() );
+ }
+
+ /**
+ * @testdox Should return WP_Error when device locale is not a string.
+ */
+ public function test_validate_rejects_non_string_for_device_locale(): void {
+ $result = PushTokenValidator::validate(
+ array( 'device_locale' => 123 ),
+ array( 'device_locale' )
+ );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertSame( 'Device locale must be a string.', $result->get_error_message() );
+ }
+
+ /**
+ * @testdox Should return WP_Error when device locale is empty.
+ */
+ public function test_validate_rejects_empty_device_locale(): void {
+ $result = PushTokenValidator::validate(
+ array( 'device_locale' => '' ),
+ array( 'device_locale' )
+ );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertSame( 'Device locale cannot be empty.', $result->get_error_message() );
+ }
+
+ /**
+ * @testdox Should return WP_Error for invalid locale formats.
+ * @dataProvider invalid_locales_provider
+ * @param string $locale The locale to test.
+ */
+ public function test_validate_rejects_invalid_formats_for_device_locale( string $locale ): void {
+ $result = PushTokenValidator::validate(
+ array( 'device_locale' => $locale ),
+ array( 'device_locale' )
+ );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertSame( 'Device locale is an invalid format.', $result->get_error_message() );
+ }
+
/**
* @testdox Should return true for a valid Apple token.
*/
@@ -751,22 +823,112 @@ class PushTokenValidatorTest extends WC_Unit_Test_Case {
$this->assertSame( 'Token is an invalid format.', $result->get_error_message() );
}
+ /**
+ * @testdox Should return true for a valid metadata array with values.
+ */
+ public function test_validate_accepts_valid_array_for_metadata(): void {
+ $this->assertTrue(
+ PushTokenValidator::validate(
+ array( 'metadata' => array( 'app_version' => '1.0.0' ) ),
+ array( 'metadata' )
+ )
+ );
+ }
+
+ /**
+ * @testdox Should return true for an empty metadata array.
+ */
+ public function test_validate_accepts_empty_array_for_metadata(): void {
+ $this->assertTrue(
+ PushTokenValidator::validate(
+ array( 'metadata' => array() ),
+ array( 'metadata' )
+ )
+ );
+ }
+
+ /**
+ * @testdox Should return WP_Error when metadata is missing.
+ */
+ public function test_validate_rejects_missing_metadata(): void {
+ $result = PushTokenValidator::validate( array(), array( 'metadata' ) );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertSame( 'Metadata is required.', $result->get_error_message() );
+ }
+
+ /**
+ * @testdox Should return WP_Error when metadata is not an array.
+ */
+ public function test_validate_rejects_non_array_for_metadata(): void {
+ $result = PushTokenValidator::validate(
+ array( 'metadata' => 'not an array' ),
+ array( 'metadata' )
+ );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertSame( 'Metadata must be an array.', $result->get_error_message() );
+ }
+
+ /**
+ * @testdox Should return WP_Error when metadata contains non-scalar values.
+ */
+ public function test_validate_rejects_non_scalar_metadata_items(): void {
+ $result = PushTokenValidator::validate(
+ array( 'metadata' => array( 'nested' => array( 'a' => 'b' ) ) ),
+ array( 'metadata' )
+ );
+
+ $this->assertInstanceOf( WP_Error::class, $result );
+ $this->assertSame( 'Metadata items must be scalar values.', $result->get_error_message() );
+ }
+
/**
* @testdox Should use the standard error code for all validation errors.
+ * @dataProvider validatable_fields_provider
+ * @param string $field The field to validate.
+ */
+ public function test_all_errors_use_standard_error_code( string $field ): void {
+ /**
+ * If field isn't platform, pass the platform in so we can check token
+ * and device_uuid.
+ */
+ $data = 'platform' === $field ? array() : array( 'platform' => PushToken::PLATFORM_APPLE );
+ $error = PushTokenValidator::validate( $data, array( $field ) );
+
+ $this->assertInstanceOf( WP_Error::class, $error );
+ $this->assertSame( PushTokenValidator::ERROR_CODE, $error->get_error_code() );
+ }
+
+ /**
+ * Data provider for valid locale formats.
+ *
+ * @return array
*/
- public function test_all_errors_use_standard_error_code(): void {
- $errors = array(
- PushTokenValidator::validate( array(), array( 'id' ) ),
- PushTokenValidator::validate( array(), array( 'user_id' ) ),
- PushTokenValidator::validate( array(), array( 'origin' ) ),
- PushTokenValidator::validate( array(), array( 'platform' ) ),
- PushTokenValidator::validate( array(), array( 'token' ) ),
+ public function valid_locales_provider(): array {
+ return array(
+ 'English US' => array( 'en_US' ),
+ 'French' => array( 'fr_FR' ),
+ 'Chinese' => array( 'zh_CN' ),
+ 'Portuguese' => array( 'pt_BR' ),
+ 'Three-letter' => array( 'ast_ES' ),
);
+ }
- foreach ( $errors as $error ) {
- $this->assertInstanceOf( WP_Error::class, $error );
- $this->assertSame( PushTokenValidator::ERROR_CODE, $error->get_error_code() );
- }
+ /**
+ * Data provider for invalid locale formats.
+ *
+ * @return array
+ */
+ public function invalid_locales_provider(): array {
+ return array(
+ 'no underscore' => array( 'enUS' ),
+ 'lowercase region' => array( 'en_gb' ),
+ 'uppercase lang' => array( 'EN_US' ),
+ 'just language' => array( 'en' ),
+ 'with hyphen' => array( 'en-US' ),
+ 'too long lang' => array( 'engl_US' ),
+ );
}
/**
@@ -786,4 +948,13 @@ class PushTokenValidatorTest extends WC_Unit_Test_Case {
public function valid_origins_provider(): array {
return array_map( fn ( $value ) => array( $value ), PushToken::ORIGINS );
}
+
+ /**
+ * Data provider for validatable fields.
+ *
+ * @return array
+ */
+ public function validatable_fields_provider(): array {
+ return array_map( fn ( $value ) => array( $value ), PushTokenValidator::VALIDATABLE_FIELDS );
+ }
}