Skip to content

test: add realistic WordPress migration acceptance test#1858

Merged
ascorbic merged 3 commits into
emdash-cms:mainfrom
swissky:test/wp-migration-acceptance
Jul 8, 2026
Merged

test: add realistic WordPress migration acceptance test#1858
ascorbic merged 3 commits into
emdash-cms:mainfrom
swissky:test/wp-migration-acceptance

Conversation

@swissky

@swissky swissky commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes #1691 (milestone 1.0): validates the WordPress migration as an end-to-end workflow instead of only as isolated importer units.

The fixture (tests/integration/wordpress-migration/fixtures/realistic-export.xml, committed — no network download) models a real small-site export:

  • 5 posts (Gutenberg blocks: paragraph/heading/list/image/quote/code; one classic-editor post with no block markers; one draft, one pending)
  • 2 pages, plus a book custom post type with a custom genre taxonomy
  • hierarchical categories (guideshowto), tags, two authors
  • 2 attachments whose size-variant URLs (hero-1024x683.jpg) are used inside content
  • featured image via _thumbnail_id, Yoast SEO meta, ACF meta, internal links, a reusable block (wp_block)

The test drives the runtime pipeline in the same order as the admin wizard — parseWxrStringpreImportWxrTaxonomiesimportContentimportReusableBlocksAsSectionsimportMediaWithProgress (SSRF-safe fetch stubbed, distinct bytes per attachment so content-hash dedupe stays honest) → rewriteUrls — and then asserts on the resulting database state:

  • every mapped item imports without errors, per-collection counts match
  • URL behavior: WP slugs preserved 1:1; content image URLs (including the size variant, via base-URL matching) and the featured image rewritten to the imported media URLs, no wp-content/uploads left behind
  • statuses mapped (publish→published, draft/pending→draft) and original post_date_gmt preserved as created_at/published_at
  • Portable Text structure (h2, list, image, blockquote, code, inline marks, classic-HTML fallback)
  • taxonomy terms created and assigned (categories, tags, custom genre scoped to the CPT), missingTaxonomies empty
  • bylines created from WXR authors and linked as primary byline
  • reusable block lands as a section; media rows enriched with real dimensions
  • re-running with skipExisting is a no-op (idempotency)

Coverage documentation (per the issue's "clearly documents what it covers"): the test header spells out what is intentionally out of scope for the WXR runtime path and where it is tested — custom-field/SEO values (plugin importer, plugin-execute-fields.test.ts), comments/menus (plugin-execute-chunked.test.ts), the redirects map (CLI two-phase import, wordpress-import.test.ts), multilingual imports (wxr-i18n-taxonomies.test.ts). The fixture still carries the Yoast/ACF meta, and the test pins that the parser preserves it for the analyze step's field suggestions.

Failures point at actionable importer problems: each assertion names the feature (slugs, dates, taxonomy assignment, URL rewriting, …) rather than a generic snapshot diff.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (n/a — test-only)
  • I have added a changeset (empty changeset — test-only, no published-package behavior change; added to satisfy the Changeset Validation check)
  • New features link to an approved Discussion: (n/a — implements the triaged milestone-1.0 issue Add realistic WordPress migration acceptance test #1691)

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: Cursor + Fable 5

Screenshots / test output

✓ tests/integration/wordpress-migration/wp-migration-acceptance.test.ts (12 tests)
✓ tests/integration/wordpress-migration + tests/integration/wordpress-import (81 tests total)

The WP migration was validated only as isolated importer units (plus a
parser/converter-only pass over the theme-unit-test dataset). Nothing
exercised the runtime pipeline the admin wizard drives, end to end,
against one realistic export.

Adds a committed WXR fixture (posts with Gutenberg + classic content,
pages, a CPT with a custom taxonomy, hierarchical categories, tags,
drafts/pending, two authors, attachments with size-variant URLs used in
content, featured images, Yoast/ACF meta, a reusable block) and an
acceptance test that runs parse -> taxonomy plan -> importContent ->
sections -> media import (network stubbed) -> URL rewriting, then
asserts the resulting database state: slugs preserved 1:1, statuses and
original dates, Portable Text structure, bylines, taxonomy terms and
assignments, sections, media enrichment, content/featured-image URLs
rewritten to imported media, and idempotent re-runs.

Out-of-scope paths (custom-field/SEO values via the plugin importer,
comments/menus, the CLI redirects map, multilingual imports) are
documented in the test header with pointers to their existing suites.

Fixes emdash-cms#1691
@changeset-bot

changeset-bot Bot commented Jul 7, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 9b6d2a1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 0 packages

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 780 lines across 2 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@pkg-pr-new

pkg-pr-new Bot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1858

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1858

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@1858

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1858

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1858

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@1858

emdash

npm i https://pkg.pr.new/emdash@1858

create-emdash

npm i https://pkg.pr.new/create-emdash@1858

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1858

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@1858

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@1858

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@1858

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@1858

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@1858

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1858

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1858

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1858

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1858

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1858

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1858

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@1858

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1858

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1858

commit: 9b6d2a1

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR adds a focused, committed-fixture acceptance test for the WordPress WXR migration pipeline (issue #1691). The approach is sound: it drives the same runtime functions the admin wizard uses, stubs network/storage deterministically, and asserts on concrete database outcomes (counts, slugs, dates, statuses, taxonomy assignments, media URL rewriting, idempotency) rather than brittle snapshots.

I read the full diff, the fixture, the new test file, and the source implementations it exercises (execute.ts, media.ts, rewrite-urls.ts, wxr-taxonomies.ts, parser.ts, sections.ts, byline/taxonomy helpers, and the test harness). Test-only PRs don't need a changeset, and there are no admin UI strings or SQL-injection concerns here. No logic errors or regressions jumped out.

One optional tightening: the “skipExisting is a no-op” assertion only guards ec_post, so a rerun that accidentally duplicates bylines or other mapped collections would not be caught. A line-anchored suggestion expands the guard to the other mapped collections and sections.

.selectFrom("ec_post" as keyof Database)
.select((eb) => eb.fn.countAll().as("count"))
.executeTakeFirstOrThrow();
expect(Number(rows.count)).toBe(5);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[suggestion] The skip-existing idempotency check currently only guards ec_post. A rerun that creates duplicate rows in ec_page, ec_book, or _emdash_sections (or fails to keep counts stable) would slip through. Tighten the assertion to cover the other mapped collections and the imported section:

Suggested change
expect(Number(rows.count)).toBe(5);
expect(Number(rows.count)).toBe(5);
const pages = await db
.selectFrom("ec_page" as keyof Database)
.select((eb) => eb.fn.countAll().as("count"))
.executeTakeFirstOrThrow();
expect(Number(pages.count)).toBe(2);
const books = await db
.selectFrom("ec_book" as keyof Database)
.select((eb) => eb.fn.countAll().as("count"))
.executeTakeFirstOrThrow();
expect(Number(books.count)).toBe(2);
const sections = await db
.selectFrom("_emdash_sections")
.select((eb) => eb.fn.countAll().as("count"))
.executeTakeFirstOrThrow();
expect(Number(sections.count)).toBe(1);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9b6d2a1 — the rerun now asserts row counts for all mapped collections (ec_post, ec_page, ec_book), _emdash_bylines, and _emdash_sections. I also went one step further than the suggestion: the test re-runs importReusableBlocksAsSections itself, so the sections count guard is backed by an actual second pass through its skip-by-slug path (asserting sectionsCreated: 0, sectionsSkipped: 1) rather than just checking that importContent didn't touch the table.

The skipExisting rerun only asserted the ec_post row count; duplicated
pages, books, bylines, or sections would have slipped through. Also
reruns the sections import so its skip-by-slug path is exercised.
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/needs-review No maintainer or bot review yet labels Jul 7, 2026
@ascorbic ascorbic added this to the 1.0 milestone Jul 8, 2026

@ascorbic ascorbic left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@ascorbic ascorbic merged commit 6fe521c into emdash-cms:main Jul 8, 2026
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core review/needs-rereview Author pushed changes since the last review size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add realistic WordPress migration acceptance test

2 participants