diff --git a/api/src/services/sap-smartedit.service.ts b/api/src/services/sap-smartedit.service.ts index ea64f8df..43304006 100644 --- a/api/src/services/sap-smartedit.service.ts +++ b/api/src/services/sap-smartedit.service.ts @@ -606,6 +606,20 @@ function rowSourceId(row: Record, block: ImpexBlock): string | n .filter((v) => v !== undefined && v !== ''); return parts.length ? parts.join('-') : null; } + +/** + * A SAP ContentPage's route: `/` for the homepage, else its label with one leading slash. + * Exports without a `homepage` column (HAC exports often omit it) fall back to SAP's + * default homepage label, `homepage`. + */ +function contentPageUrl(row: Record): string | null { + const label = (row.label ?? '').trim(); + const isHome = row.homepage === undefined + ? /^\/?homepage$/i.test(label) + : String(row.homepage).trim().toLowerCase() === 'true'; + if (isHome) return '/'; + return label ? `/${label.replace(/^\/+/, '')}` : null; +} interface Counters { assetsSkipped: number; refsSkipped: number } /** Transform one ImpEx cell value to its Contentstack field value, by target type. */ @@ -942,6 +956,14 @@ async function createEntry( if (defaultLabel || !existingEntry) { if (!entry.title || !unambiguousDefault) entry.title = id; } + // Every CT is a page with a mandatory `url` (ensureMandatoryFields), but SAP + // ContentPage has no url column: its route is the `label` (/faq), and the + // homepage flag means `/`. Without this every migrated page had an empty url, + // so a storefront querying pages by url found none. + if (srcType === 'ContentPage' && !entry.url) { + const pageUrl = contentPageUrl(row); + if (pageUrl) entry.url = pageUrl; + } entryData[uid] = entry; } diff --git a/api/tests/fixtures/sap-smartedit/contentpage-url-no-flag.impex b/api/tests/fixtures/sap-smartedit/contentpage-url-no-flag.impex new file mode 100644 index 00000000..dca9b0f4 --- /dev/null +++ b/api/tests/fixtures/sap-smartedit/contentpage-url-no-flag.impex @@ -0,0 +1,3 @@ +INSERT_UPDATE ContentPage; uid[unique=true]; name; label + ; home; "Home"; homepage + ; about; "About"; about diff --git a/api/tests/fixtures/sap-smartedit/contentpage-url.impex b/api/tests/fixtures/sap-smartedit/contentpage-url.impex new file mode 100644 index 00000000..7777bb4e --- /dev/null +++ b/api/tests/fixtures/sap-smartedit/contentpage-url.impex @@ -0,0 +1,10 @@ +INSERT_UPDATE ContentPage; uid[unique=true]; name; label; homepage + ; homepage; "Homepage"; homepage; true + ; faq; "FAQ"; /faq; false + ; shipping; "Shipping"; shipping; false + ; nolabel; "No Label"; ; false + ; campaign; "Campaign"; campaign; TRUE + ; legacyhome; "Legacy Home"; homepage; false + +UPDATE ContentPage; uid[unique=true]; name + ; faq; "FAQ" diff --git a/api/tests/unit/services/sap-smartedit.service.test.ts b/api/tests/unit/services/sap-smartedit.service.test.ts index 1101fea5..d565e2aa 100644 --- a/api/tests/unit/services/sap-smartedit.service.test.ts +++ b/api/tests/unit/services/sap-smartedit.service.test.ts @@ -1343,3 +1343,96 @@ describe('sap-smartedit getAllAssets/createEntry — a genuine directory as the expect(fs.readFileSync(onDisk).equals(fs.readFileSync(src))).toBe(true); }); }); + +/** + * Every content type is created as a page with a mandatory `url`, but SAP + * ContentPage has no url column — its route lives in `label`, and the homepage + * is flagged `homepage=true`. Every migrated page used to get an empty url, so + * a storefront looking pages up by url (Spartacus) found none of them. + */ +describe('sap-smartedit createEntry — ContentPage url from label/homepage', () => { + const STACK_URL = 'test-stack-sap-smartedit-contentpage-url'; + const OUT_URL = path.join(process.cwd(), './cmsMigrationData', STACK_URL); + const URL_FIXTURE = path.join(__dirname, '../../fixtures/sap-smartedit/contentpage-url.impex'); + const CT = { + otherCmsTitle: 'ContentPage', contentstackUid: 'cs_contentpage', + fieldMapping: [ + { otherCmsField: 'name', contentstackFieldUid: 'title', contentstackFieldType: 'single_line_text' }, + { otherCmsField: 'label', contentstackFieldUid: 'label', contentstackFieldType: 'single_line_text' }, + ], + }; + let byTitle: Record; + + beforeAll(async () => { + fs.rmSync(OUT_URL, { recursive: true, force: true }); + await sapSmarteditService.createEntry(URL_FIXTURE, '', STACK_URL, 'test-project', [CT], {}, 'en-us', {}); + const entries = JSON.parse(fs.readFileSync(path.join(OUT_URL, 'entries', 'cs_contentpage', 'en-us', 'en-us.json'), 'utf8')); + byTitle = Object.fromEntries(Object.values(entries).map((e: any) => [e.title, e])); + }); + + afterAll(() => { + fs.rmSync(OUT_URL, { recursive: true, force: true }); + }); + + it('gives the homepage the root url', () => { + expect(byTitle.Homepage.url).toBe('/'); + }); + + it('uses a label that is already a path as-is', () => { + expect(byTitle.FAQ.url).toBe('/faq'); + }); + + it('adds the leading slash to a bare label', () => { + expect(byTitle.Shipping.url).toBe('/shipping'); + }); + + it('writes no url when the page has neither label nor homepage flag', () => { + expect('url' in byTitle['No Label']).toBe(false); + }); + + it('keeps the url when a later partial UPDATE row carries no label', () => { + expect(byTitle.FAQ.url).toBe('/faq'); + }); + + it('reads the homepage flag in any case (TRUE)', () => { + expect(byTitle.Campaign.url).toBe('/'); + }); + + it('keeps /homepage for a page labelled homepage whose flag is false', () => { + expect(byTitle['Legacy Home'].url).toBe('/homepage'); + }); +}); + +/** HAC exports often have no `homepage` column; SAP's default homepage label is `homepage`. */ +describe('sap-smartedit createEntry — ContentPage url without a homepage column', () => { + const STACK_NOFLAG = 'test-stack-sap-smartedit-contentpage-url-no-flag'; + const OUT_NOFLAG = path.join(process.cwd(), './cmsMigrationData', STACK_NOFLAG); + const NOFLAG_FIXTURE = path.join(__dirname, '../../fixtures/sap-smartedit/contentpage-url-no-flag.impex'); + const CT = { + otherCmsTitle: 'ContentPage', contentstackUid: 'cs_contentpage', + fieldMapping: [ + { otherCmsField: 'name', contentstackFieldUid: 'title', contentstackFieldType: 'single_line_text' }, + { otherCmsField: 'label', contentstackFieldUid: 'label', contentstackFieldType: 'single_line_text' }, + ], + }; + let byTitle: Record; + + beforeAll(async () => { + fs.rmSync(OUT_NOFLAG, { recursive: true, force: true }); + await sapSmarteditService.createEntry(NOFLAG_FIXTURE, '', STACK_NOFLAG, 'test-project', [CT], {}, 'en-us', {}); + const entries = JSON.parse(fs.readFileSync(path.join(OUT_NOFLAG, 'entries', 'cs_contentpage', 'en-us', 'en-us.json'), 'utf8')); + byTitle = Object.fromEntries(Object.values(entries).map((e: any) => [e.title, e])); + }); + + afterAll(() => { + fs.rmSync(OUT_NOFLAG, { recursive: true, force: true }); + }); + + it('gives the page labelled homepage the root url', () => { + expect(byTitle.Home.url).toBe('/'); + }); + + it('still routes other pages by label', () => { + expect(byTitle.About.url).toBe('/about'); + }); +});