diff --git a/tests/fixtures/wix/README.md b/tests/fixtures/wix/README.md new file mode 100644 index 00000000..a5d542ba --- /dev/null +++ b/tests/fixtures/wix/README.md @@ -0,0 +1,98 @@ +# Wix Product Option Fixture + +## Scope + +This fixture provides synthetic Wix Stores product options and variants for +future product synchronization work. + +It does not make live Wix API calls and does not implement a Wix connector. + +## Official Wix documentation + +The fixture is based on the Wix Stores Catalog V3 product model. + +- Wix Stores introduction: + https://dev.wix.com/docs/api-reference/business-solutions/stores/introduction + +- Product options and variants: + https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/about-product-options-and-variants + +- Products V3: + https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/introduction + +- Query Products: + https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/query-products + +## Synthetic product + +The fixture contains one synthetic product: + +- Product: `Classic T-Shirt` +- Option 1: `Size` + - `Small` + - `Large` +- Option 2: `Color` + - `Red` + - `Blue` + +The four option combinations are represented as separate synthetic variants. + +## Expected Posnic mapping + +The repository already uses the following item fields in its import fixtures: + +- `SKU` -> `itemid` +- `Barcode` -> `barcode_id` + +The existing variant-linking script also uses: + +- `variant_group_id` +- `variant_axis` +- `variant_value` +- `variant_parent_name` + +For this fixture, the expected future mapping is: + +| Wix data | Expected Posnic mapping | +|---|---| +| Product name | `variant_parent_name` | +| Option combination | `variant_value` | +| Variant family | `variant_axis` | +| Variant SKU | `itemid` | +| Variant barcode | `barcode_id` | + +For example: + +`Small + Red` becomes the synthetic variant value `Small - Red` +under the parent product `Classic T-Shirt`. + +This fixture documents an expected mapping for future connector work. It does +not claim that a Wix connector implementing this mapping already exists. + +## Missing SKU and barcode behavior + +A missing SKU must not result in an invented identifier. + +- Missing SKU -> `itemid` remains unset and the variant is marked for review. +- Missing barcode -> `barcode_id` remains unset. +- Missing SKU and barcode -> both remain unset and the variant is marked for review. + +The synthetic variant ID remains available for fixture-level identification. + +## Unsupported areas + +This fixture does not establish or implement: + +- live Wix API calls; +- Wix authentication or installation; +- a production Wix connector; +- automatic product synchronization; +- SKU generation; +- barcode generation; +- Posnic Cloud-only functionality; +- platform certification. + +All identifiers in this fixture are synthetic and intended only for local +development, testing, and documentation. + +It contains no live credentials, merchant data, or customer data. diff --git a/tests/fixtures/wix/product-options.json b/tests/fixtures/wix/product-options.json new file mode 100644 index 00000000..bb449f38 --- /dev/null +++ b/tests/fixtures/wix/product-options.json @@ -0,0 +1,94 @@ +{ + "provider": "wix", + "catalog_version": "v3", + "product": { + "id": "SYNTH-WIX-PROD-1001", + "name": "Classic T-Shirt", + "options": [ + { + "name": "Size", + "choices": [ + "Small", + "Large" + ] + }, + { + "name": "Color", + "choices": [ + "Red", + "Blue" + ] + } + ], + "variants": [ + { + "id": "SYNTH-WIX-VAR-1001", + "choices": { + "Size": "Small", + "Color": "Red" + }, + "sku": "SYNTH-SKU-1001", + "barcode": "SYNTH-BC-1001", + "expected_posnic": { + "variant_parent_name": "Classic T-Shirt", + "variant_axis": "Variant", + "variant_value": "Small - Red", + "itemid": "SYNTH-SKU-1001", + "barcode_id": "SYNTH-BC-1001", + "review": false + } + }, + { + "id": "SYNTH-WIX-VAR-1002", + "choices": { + "Size": "Small", + "Color": "Blue" + }, + "sku": "SYNTH-SKU-1002", + "barcode": null, + "expected_posnic": { + "variant_parent_name": "Classic T-Shirt", + "variant_axis": "Variant", + "variant_value": "Small - Blue", + "itemid": "SYNTH-SKU-1002", + "barcode_id": null, + "review": false + } + }, + { + "id": "SYNTH-WIX-VAR-1003", + "choices": { + "Size": "Large", + "Color": "Red" + }, + "sku": null, + "barcode": "SYNTH-BC-1003", + "expected_posnic": { + "variant_parent_name": "Classic T-Shirt", + "variant_axis": "Variant", + "variant_value": "Large - Red", + "itemid": null, + "barcode_id": "SYNTH-BC-1003", + "review": true + } + }, + { + "id": "SYNTH-WIX-VAR-1004", + "choices": { + "Size": "Large", + "Color": "Blue" + }, + "sku": null, + "barcode": null, + "expected_posnic": { + "variant_parent_name": "Classic T-Shirt", + "variant_axis": "Variant", + "variant_value": "Large - Blue", + "itemid": null, + "barcode_id": null, + "review": true + } + } + ] + } +} diff --git a/tests/wix-product-option-fixture.test.js b/tests/wix-product-option-fixture.test.js new file mode 100644 index 00000000..b9caea06 --- /dev/null +++ b/tests/wix-product-option-fixture.test.js @@ -0,0 +1,149 @@ +'use strict'; + +const test = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); + +const FIXTURE_PATH = path.join( + __dirname, + 'fixtures', + 'wix', + 'product-options.json', +); + +function readFixture() { + return JSON.parse(fs.readFileSync(FIXTURE_PATH, 'utf8')); +} + +test('Wix fixture contains size and color product options', () => { + const fixture = readFixture(); + const options = fixture.product.options; + + assert.strictEqual(fixture.provider, 'wix'); + assert.strictEqual(fixture.catalog_version, 'v3'); + assert.strictEqual(fixture.product.name, 'Classic T-Shirt'); + + assert.deepStrictEqual(options[0], { + name: 'Size', + choices: ['Small', 'Large'], + }); + + assert.deepStrictEqual(options[1], { + name: 'Color', + choices: ['Red', 'Blue'], + }); +}); + +test('Wix fixture contains every size and color combination', () => { + const fixture = readFixture(); + const variants = fixture.product.variants; + + assert.strictEqual(variants.length, 4); + + const combinations = variants.map( + (variant) => + `${variant.choices.Size} - ${variant.choices.Color}`, + ); + + assert.deepStrictEqual(combinations.sort(), [ + 'Large - Blue', + 'Large - Red', + 'Small - Blue', + 'Small - Red', + ]); +}); + +test('Wix fixture maps SKU and barcode to Posnic identifiers', () => { + const fixture = readFixture(); + + for (const variant of fixture.product.variants) { + assert.strictEqual( + variant.expected_posnic.itemid, + variant.sku, + ); + + assert.strictEqual( + variant.expected_posnic.barcode_id, + variant.barcode, + ); + } +}); +test('Wix variants map option choices to the Posnic variant fields', () => { + const fixture = readFixture(); + + for (const variant of fixture.product.variants) { + const expectedValue = + `${variant.choices.Size} - ${variant.choices.Color}`; + + assert.strictEqual( + variant.expected_posnic.variant_parent_name, + fixture.product.name, + ); + + assert.strictEqual( + variant.expected_posnic.variant_axis, + 'Variant', + ); + + assert.strictEqual( + variant.expected_posnic.variant_value, + expectedValue, + ); + } +}); + +test('Wix fixture covers missing SKU and barcode behavior', () => { + const fixture = readFixture(); + + const missingSku = fixture.product.variants.filter( + (variant) => variant.sku === null, + ); + + const missingBarcode = fixture.product.variants.filter( + (variant) => variant.barcode === null, + ); + + assert.strictEqual(missingSku.length, 2); + assert.strictEqual(missingBarcode.length, 2); + + for (const variant of fixture.product.variants) { + assert.strictEqual( + variant.expected_posnic.review, + variant.sku === null, + ); + } +}); + +test('Wix fixture contains only synthetic identifiers', () => { + const fixture = readFixture(); + + assert.match(fixture.product.id, /^SYNTH-WIX-PROD-\d+$/); + + for (const variant of fixture.product.variants) { + assert.match(variant.id, /^SYNTH-WIX-VAR-\d+$/); + + if (variant.sku !== null) { + assert.match(variant.sku, /^SYNTH-SKU-\d+$/); + } + + if (variant.barcode !== null) { + assert.match(variant.barcode, /^SYNTH-BC-\d+$/); + } + } +}); + +test('Wix fixture documentation describes the integration boundary', () => { + const documentation = fs.readFileSync( + path.join(__dirname, 'fixtures', 'wix', 'README.md'), + 'utf8', + ); + + assert.match(documentation, /Official Wix documentation/i); + assert.match(documentation, /Product options and variants/i); + assert.match(documentation, /Expected Posnic mapping/i); + assert.match(documentation, /Missing SKU and barcode behavior/i); + assert.match(documentation, /Unsupported areas/i); + assert.match(documentation, /live Wix API calls/i); + assert.match(documentation, /synthetic/i); +});