This commit introduces new fields `syncHash` and `syncImageHash` to the `listing` and `listingVariant` schemas, enhancing the tracking of synchronization states. Additionally, a new module for sync fingerprinting is added, which includes functions to generate hashes for listings and their variants. The marketplace integration logic is updated to utilize these hashes for determining if updates are necessary, improving efficiency in syncing operations. Tests are also added to ensure the correctness of the new functionality and its integration with existing systems.
109 lines
3.4 KiB
JavaScript
109 lines
3.4 KiB
JavaScript
import { describe, expect, it } from '@jest/globals';
|
|
import {
|
|
hashSyncPayload,
|
|
imageFilesHash,
|
|
isUnsyncedDraftListing,
|
|
listingImageSyncHash,
|
|
listingImagesUnchanged,
|
|
listingSyncFingerprint,
|
|
listingSyncHash,
|
|
listingSyncUnchanged,
|
|
payloadsEqual,
|
|
} from '../syncFingerprint.js';
|
|
|
|
const listing = {
|
|
title: 'Widget',
|
|
description: 'A widget',
|
|
condition: 'new',
|
|
stockLocation: { _id: 'loc-1' },
|
|
courierServices: [{ _id: 'cs-2' }, { _id: 'cs-1' }],
|
|
fulfillmentPolicy: 'ful-1',
|
|
paymentPolicy: { _id: 'pay-1' },
|
|
returnPolicy: { _id: 'ret-1' },
|
|
listingImages: [{ _id: 'img-1' }],
|
|
updatedAt: new Date('2026-01-01'),
|
|
lastSyncedAt: new Date('2026-01-02'),
|
|
state: { type: 'active' },
|
|
};
|
|
|
|
const variants = [
|
|
{
|
|
_reference: 'SKU-RED',
|
|
price: 9.99,
|
|
currency: 'GBP',
|
|
stockQuantity: 3,
|
|
aspects: [
|
|
{ name: 'Size', value: 'L' },
|
|
{ name: 'Color', value: 'Red' },
|
|
],
|
|
listingImages: [],
|
|
},
|
|
];
|
|
|
|
describe('syncFingerprint', () => {
|
|
it('hashes the same listing payload consistently and ignores timestamps and state', () => {
|
|
const first = listingSyncHash(listing, variants);
|
|
const second = listingSyncHash(
|
|
{ ...listing, updatedAt: new Date(), lastSyncedAt: new Date(), state: { type: 'syncing' } },
|
|
variants
|
|
);
|
|
expect(first).toBe(second);
|
|
expect(listingSyncFingerprint(listing, variants).v).toBe(1);
|
|
});
|
|
|
|
it('changes the hash when price, images, or policy ids change', () => {
|
|
const baseline = listingSyncHash(listing, variants);
|
|
expect(
|
|
listingSyncHash(listing, [{ ...variants[0], price: 12.5 }])
|
|
).not.toBe(baseline);
|
|
expect(
|
|
listingSyncHash({ ...listing, listingImages: [{ _id: 'img-2' }] }, variants)
|
|
).not.toBe(baseline);
|
|
expect(
|
|
listingSyncHash({ ...listing, paymentPolicy: 'pay-2' }, variants)
|
|
).not.toBe(baseline);
|
|
});
|
|
|
|
it('treats a stored syncHash match as unchanged', () => {
|
|
const syncHash = listingSyncHash(listing, variants);
|
|
expect(listingSyncUnchanged({ ...listing, syncHash }, variants)).toBe(true);
|
|
expect(listingSyncUnchanged({ ...listing, syncHash: 'nope' }, variants)).toBe(false);
|
|
expect(listingSyncUnchanged(listing, variants)).toBe(false);
|
|
});
|
|
|
|
it('identifies unsynced draft listings', () => {
|
|
expect(isUnsyncedDraftListing({ state: { type: 'draft' } })).toBe(true);
|
|
expect(
|
|
isUnsyncedDraftListing({ state: { type: 'draft' }, externalReference: '123' })
|
|
).toBe(false);
|
|
expect(isUnsyncedDraftListing({ state: { type: 'active' } })).toBe(false);
|
|
});
|
|
|
|
it('hashes image file ids independently of listing copy', () => {
|
|
const imageHash = listingImageSyncHash(listing, variants);
|
|
expect(
|
|
listingImageSyncHash({ ...listing, title: 'Renamed' }, variants)
|
|
).toBe(imageHash);
|
|
expect(
|
|
listingImageSyncHash({ ...listing, listingImages: [{ _id: 'img-2' }] }, variants)
|
|
).not.toBe(imageHash);
|
|
expect(
|
|
listingImagesUnchanged(
|
|
{
|
|
...listing,
|
|
syncImageHash: imageFilesHash(listing.listingImages),
|
|
marketplaceImageUrls: ['https://i.ebayimg.com/1.jpg'],
|
|
},
|
|
variants
|
|
)
|
|
).toBe(true);
|
|
expect(imageFilesHash([{ _id: 'img-1' }])).toBe(imageFilesHash(['img-1']));
|
|
});
|
|
|
|
it('compares payloads by canonical hash', () => {
|
|
expect(payloadsEqual({ b: 1, a: 2 }, { a: 2, b: 1 })).toBe(true);
|
|
expect(payloadsEqual({ a: 1 }, { a: 2 })).toBe(false);
|
|
expect(hashSyncPayload({ a: 1 })).toHaveLength(64);
|
|
});
|
|
});
|