diff --git a/.gitignore b/.gitignore index e2b7aea..177e16c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ yarn-error.log* _build src/version.ts +tests/fixtures/.tmp-deposit.xml .yalc yalc.lock diff --git a/README.md b/README.md index 0de8ac9..1526357 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ Utilities for creating crossref.org deposit metadata from node or the command line. +CLI e2e fixtures and golden XML: [`docs/e2e-fixtures.md`](./docs/e2e-fixtures.md). + To use from the command line, use the `-g` to create a global install. ``` diff --git a/docs/e2e-fixtures.md b/docs/e2e-fixtures.md new file mode 100644 index 0000000..29da0a8 --- /dev/null +++ b/docs/e2e-fixtures.md @@ -0,0 +1,65 @@ +# CLI end-to-end fixtures + +Regression fixtures for `crossref deposit` (and later `validate`). Kept **compact and owned** by this repo — shaped like SciPy proceedings papers, not wholesale copies of [scipy_proceedings/papers](https://github.com/scipy-conference/scipy_proceedings/tree/2025/papers). + +## Goals + +- Exercise real CLI → MyST load → deposit XML path +- Capture **golden XML from `main`** for regression against refactors (e.g. monorepo split) +- Stay small and maintainable (< ~100KB text) + +## Layout + +``` +tests/fixtures/ + shared/ + proceedings.yml # SciPy-like venue / volume / editors (synthetic) + conference/ + paper-a/ # ORCID, affiliations, pages, abstract, DOI cites + paper-b/ # equal_contributor, funding, subtitle + journal/ + article/ + preprint/ + article/ + dataset/ + item/ + golden/ # Normalized XML baselines + conference.xml + journal.xml + preprint.xml + dataset.xml +``` + +## Coverage matrix + +| Fixture | `--type` | What it covers | +| -------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------- | +| `conference/paper-a` + `paper-b` | `conference` | Multi-paper batch, venue/volume/editors, pages, abstract, ORCID, funding, equal contributors, DOI citations | +| `journal/article` | `journal` | Journal title/DOI, volume/issue | +| `preprint/article` | `preprint` | Standalone posted content | +| `dataset/item` | `dataset` | Database venue + dataset record | + +## Design choices + +- **From scratch** (not vendored SciPy trees): flatten `extends`, no images/templates, fake names/emails/ORCIDs. +- **DOIs always present** in frontmatter so deposits are non-interactive (no DOI checkbox prompts). +- **Stable batch id** via `--id` in tests; **normalize** `timestamp` (and any remaining UUIDs) before comparing to goldens. +- Depositor flags use CLI defaults or explicit `--name` / `--email` / `--registrant`. + +## Running + +```bash +npm test # unit + e2e (builds CLI for e2e) +npm run test:unit # unit only +npm run test:e2e # e2e only (builds CLI first) +npm run test:e2e:record # regenerates tests/fixtures/golden/*.xml +``` + +`test:e2e:record` should only be used intentionally (e.g. when deposit XML shape changes on purpose). + +## Out of scope (v1) + +- Inquirer / path-discovery flows +- DOI generation UI +- Submodule clone of full SciPy proceedings +- Schema validation e2e (separate; needs schema bundles) diff --git a/package.json b/package.json index be19a32..431f46a 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,10 @@ "unlink": "npm uninstall -g crossref-utils;", "link": "npm run unlink; npm link;", "dev": "npm run copy:version && npm run link && esbuild src/cli/index.ts --bundle --outfile=dist/crossref.cjs --platform=node --external:fsevents --watch", - "test": "npm run copy:version && vitest run", + "test": "npm run copy:version && vitest run && npm run test:e2e", + "test:unit": "npm run copy:version && vitest run", + "test:e2e": "npm run build && npm run copy:version && vitest run --config vitest.e2e.config.ts", + "test:e2e:record": "npm run build && npm run copy:version && RECORD_GOLDEN=1 vitest run --config vitest.e2e.config.ts", "test:watch": "npm run copy:version && vitest watch", "lint": "eslint \"src/**/*.ts*\" -c ./.eslintrc.cjs", "lint:format": "prettier --check \"src/**/*.{ts,tsx,md}\"", diff --git a/tests/cli.e2e.spec.ts b/tests/cli.e2e.spec.ts new file mode 100644 index 0000000..367a79c --- /dev/null +++ b/tests/cli.e2e.spec.ts @@ -0,0 +1,134 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import { describe, test, expect, beforeAll } from 'vitest'; + +const fixturesRoot = path.join(process.cwd(), 'tests/fixtures'); +const goldenRoot = path.join(fixturesRoot, 'golden'); +const cliBin = path.join(process.cwd(), 'dist/crossref.cjs'); +const repoRoot = process.cwd(); + +const RECORD = process.env.RECORD_GOLDEN === '1'; + +/** Strip nondeterministic deposit head fields for stable comparisons. */ +export function normalizeDepositXml(xml: string): string { + return xml + .replace(/[^<]*<\/doi_batch_id>/g, 'FIXED_BATCH_ID') + .replace(/[^<]*<\/timestamp>/g, '0') + .replace(/\r\n/g, '\n') + .trim(); +} + +function runDeposit(args: string[]): string { + const result = spawnSync(process.execPath, [cliBin, 'deposit', ...args], { + cwd: repoRoot, + encoding: 'utf8', + env: { ...process.env, NO_COLOR: '1' }, + }); + if (result.status !== 0) { + throw new Error( + `crossref deposit failed (${result.status}):\n${result.stdout}\n${result.stderr}`, + ); + } + return result.stdout; +} + +function depositToNormalizedXml(args: string[]): string { + const outFile = path.join(fixturesRoot, '.tmp-deposit.xml'); + try { + runDeposit([...args, '--id', 'FIXED_BATCH_ID', '-o', outFile]); + const xml = fs.readFileSync(outFile, 'utf8'); + return normalizeDepositXml(xml); + } finally { + if (fs.existsSync(outFile)) fs.unlinkSync(outFile); + } +} + +function assertOrRecord(name: string, actual: string) { + const goldenPath = path.join(goldenRoot, name); + if (RECORD) { + fs.mkdirSync(goldenRoot, { recursive: true }); + fs.writeFileSync(goldenPath, `${actual}\n`); + return; + } + if (!fs.existsSync(goldenPath)) { + throw new Error(`Missing golden ${goldenPath}. Run with RECORD_GOLDEN=1 to create baselines.`); + } + const expected = normalizeDepositXml(fs.readFileSync(goldenPath, 'utf8')); + expect(actual).toBe(expected); +} + +describe('CLI e2e deposit fixtures', () => { + beforeAll(() => { + if (!fs.existsSync(cliBin)) { + throw new Error(`CLI not built: ${cliBin}. Run npm run build first.`); + } + }); + + test('conference multi-paper deposit', () => { + const paperA = path.join(fixturesRoot, 'conference/paper-a'); + const paperB = path.join(fixturesRoot, 'conference/paper-b'); + const actual = depositToNormalizedXml([ + '--type', + 'conference', + '--name', + 'Fixture Depositor', + '--email', + 'depositor@example.org', + paperA, + paperB, + ]); + assertOrRecord('conference.xml', actual); + expect(actual).toContain('conference_paper'); + expect(actual).toContain('10.99999/fixture.paper-a'); + expect(actual).toContain('10.99999/fixture.paper-b'); + }, 120000); + + test('journal deposit', () => { + const article = path.join(fixturesRoot, 'journal/article'); + const actual = depositToNormalizedXml([ + '--type', + 'journal', + '--name', + 'Fixture Depositor', + '--email', + 'depositor@example.org', + article, + ]); + assertOrRecord('journal.xml', actual); + expect(actual).toContain('journal_article'); + expect(actual).toContain('10.99999/fixture.journal-article'); + }, 120000); + + test('preprint deposit', () => { + const article = path.join(fixturesRoot, 'preprint/article'); + const actual = depositToNormalizedXml([ + '--type', + 'preprint', + '--name', + 'Fixture Depositor', + '--email', + 'depositor@example.org', + article, + ]); + assertOrRecord('preprint.xml', actual); + expect(actual).toContain('posted_content'); + expect(actual).toContain('10.99999/fixture.preprint'); + }, 120000); + + test('dataset deposit', () => { + const item = path.join(fixturesRoot, 'dataset/item'); + const actual = depositToNormalizedXml([ + '--type', + 'dataset', + '--name', + 'Fixture Depositor', + '--email', + 'depositor@example.org', + item, + ]); + assertOrRecord('dataset.xml', actual); + expect(actual).toContain('dataset'); + expect(actual).toContain('10.99999/fixture.dataset'); + }, 120000); +}); diff --git a/tests/fixtures/conference/paper-a/main.md b/tests/fixtures/conference/paper-a/main.md new file mode 100644 index 0000000..524f5ec --- /dev/null +++ b/tests/fixtures/conference/paper-a/main.md @@ -0,0 +1,10 @@ +--- +title: Compact fixtures for Crossref conference deposits +abstract: | + This synthetic abstract exercises deposit metadata extraction for conference papers. + It references a well-known paper with a DOI [](doi:10.25080/issn.2575-9752). +--- + +# Introduction + +A short body paragraph with a citation to ensure DOI citations are collected [](doi:10.1038/nature14539). diff --git a/tests/fixtures/conference/paper-a/myst.yml b/tests/fixtures/conference/paper-a/myst.yml new file mode 100644 index 0000000..2f174e8 --- /dev/null +++ b/tests/fixtures/conference/paper-a/myst.yml @@ -0,0 +1,31 @@ +version: 1 +extends: ../../shared/proceedings.yml +project: + title: Compact fixtures for Crossref conference deposits + subtitle: A short synthetic article + description: | + Synthetic fixture abstract used for CLI end-to-end regression tests. + first_page: 1 + last_page: 8 + doi: 10.99999/fixture.paper-a + date: 2025-07-10 + authors: + - name: Ada Author + email: ada@example.org + orcid: 0000-0001-2345-6789 + corresponding: true + affiliations: + - Example University + - name: Blake Writer + email: blake@example.org + orcid: 0000-0002-3456-7890 + affiliations: + - Example Lab + affiliations: + - id: Example University + name: Example University + ror: https://ror.org/05ggc9x63 + - id: Example Lab + name: Example Lab + toc: + - file: main.md diff --git a/tests/fixtures/conference/paper-b/main.md b/tests/fixtures/conference/paper-b/main.md new file mode 100644 index 0000000..c07b2e3 --- /dev/null +++ b/tests/fixtures/conference/paper-b/main.md @@ -0,0 +1,9 @@ +--- +title: Second fixture paper with funding +abstract: | + Synthetic abstract for funding and equal-contributor coverage in conference deposits. +--- + +# Notes + +Body text for paper B. diff --git a/tests/fixtures/conference/paper-b/myst.yml b/tests/fixtures/conference/paper-b/myst.yml new file mode 100644 index 0000000..0329261 --- /dev/null +++ b/tests/fixtures/conference/paper-b/myst.yml @@ -0,0 +1,38 @@ +version: 1 +extends: ../../shared/proceedings.yml +project: + title: Second fixture paper with funding + subtitle: Equal contributors and awards + description: Second synthetic article for multi-paper conference deposits. + first_page: 9 + last_page: 12 + doi: 10.99999/fixture.paper-b + date: 2025-07-10 + authors: + - name: Casey Coauthor + email: casey@example.org + orcid: 0000-0003-4567-8901 + equal_contributor: true + affiliations: + - Example Institute + - name: Dana Coauthor + email: dana@example.org + orcid: 0000-0004-5678-9012 + equal_contributor: true + affiliations: + - Example Institute + affiliations: + - id: Example Institute + name: Example Institute + ror: https://ror.org/02mhbdp94 + - id: Example Fund + name: Example Fund + institution: Example Funding Agency + funding: + - statement: Supported by Example Fund. + awards: + - id: EF-2025-001 + sources: + - Example Fund + toc: + - file: main.md diff --git a/tests/fixtures/dataset/item/main.md b/tests/fixtures/dataset/item/main.md new file mode 100644 index 0000000..31df702 --- /dev/null +++ b/tests/fixtures/dataset/item/main.md @@ -0,0 +1,9 @@ +--- +title: Compact dataset fixture +abstract: | + Synthetic dataset description used as deposit abstract/description. +--- + +# Data + +Dataset fixture notes. diff --git a/tests/fixtures/dataset/item/myst.yml b/tests/fixtures/dataset/item/myst.yml new file mode 100644 index 0000000..480f4a4 --- /dev/null +++ b/tests/fixtures/dataset/item/myst.yml @@ -0,0 +1,20 @@ +version: 1 +project: + title: Compact dataset fixture + description: Synthetic dataset record for CLI e2e tests. + doi: 10.99999/fixture.dataset + date: 2025-02-01 + zenodo: https://zenodo.org/records/9999999 + venue: + title: Example Research Data Repository + doi: 10.99999/example-data + authors: + - name: Sam Scientist + email: sam@example.org + affiliations: + - Example Data Lab + affiliations: + - id: Example Data Lab + name: Example Data Lab + toc: + - file: main.md diff --git a/tests/fixtures/golden/conference.xml b/tests/fixtures/golden/conference.xml new file mode 100644 index 0000000..d9a0397 --- /dev/null +++ b/tests/fixtures/golden/conference.xml @@ -0,0 +1 @@ +FIXED_BATCH_ID0Fixture Depositordepositor@example.orgCrossrefAlexEditorExample Universityhttps://ror.org/05ggc9x63Alex EditorBlairChairExample LabBlair ChairExample Science Conference, 2025ExSci1stExample CityJuly 1 - July 5, 2025Proceedings of the Example Science ConferenceProceedings of the Example Science Conference0000-000010.99999/issn.0000-0000https://doi.curvenote.com/10.99999/issn.0000-0000Proceedings of the 1st Example Science ConferenceScientific ComputingExample Proceedings Press0710202510.99999/proc.2025https://doi.curvenote.com/10.99999/proc.2025AdaAuthorExample Universityhttps://ror.org/05ggc9x63https://orcid.org/0000-0001-2345-6789Ada AuthorBlakeWriterExample Labhttps://orcid.org/0000-0002-3456-7890Blake WriterCompact fixtures for Crossref conference depositsA short synthetic articleSynthetic fixture abstract used for CLI end-to-end regression tests.0710202518https://creativecommons.org/licenses/by/4.0/10.99999/fixture.paper-ahttps://doi.curvenote.com/10.99999/fixture.paper-a10.1038/nature14539CaseyCoauthorExample Institutehttps://ror.org/02mhbdp94https://orcid.org/0000-0003-4567-8901Casey CoauthorDanaCoauthorExample Institutehttps://ror.org/02mhbdp94https://orcid.org/0000-0004-5678-9012Dana CoauthorSecond fixture paper with fundingEqual contributors and awardsSecond synthetic article for multi-paper conference deposits.07102025912https://creativecommons.org/licenses/by/4.0/10.99999/fixture.paper-bhttps://doi.curvenote.com/10.99999/fixture.paper-b diff --git a/tests/fixtures/golden/dataset.xml b/tests/fixtures/golden/dataset.xml new file mode 100644 index 0000000..8f10ce2 --- /dev/null +++ b/tests/fixtures/golden/dataset.xml @@ -0,0 +1 @@ +FIXED_BATCH_ID0Fixture Depositordepositor@example.orgCrossrefExample Research Data RepositorySamScientistExample Data LabSam ScientistCompact dataset fixture02012025Synthetic dataset record for CLI e2e tests.10.99999/example-data10.99999/fixture.datasethttps://zenodo.org/records/9999999 diff --git a/tests/fixtures/golden/journal.xml b/tests/fixtures/golden/journal.xml new file mode 100644 index 0000000..91a98c8 --- /dev/null +++ b/tests/fixtures/golden/journal.xml @@ -0,0 +1 @@ +FIXED_BATCH_ID0Fixture Depositordepositor@example.orgCrossrefExample Journal of ComputingExJ Comp10.99999/exjcomphttps://doi.curvenote.com/10.99999/exjcomp0301202512310.99999/exjcomp.12.3https://doi.curvenote.com/10.99999/exjcomp.12.3Compact journal fixture articleJordanJournalerExample Collegehttps://orcid.org/0000-0005-6789-0123Jordan JournalerSynthetic journal article for CLI e2e tests.030120251015https://creativecommons.org/licenses/by/4.0/10.99999/fixture.journal-articlehttps://doi.curvenote.com/10.99999/fixture.journal-article10.1145/3290605.3300233 diff --git a/tests/fixtures/golden/preprint.xml b/tests/fixtures/golden/preprint.xml new file mode 100644 index 0000000..e603ae7 --- /dev/null +++ b/tests/fixtures/golden/preprint.xml @@ -0,0 +1 @@ +FIXED_BATCH_ID0Fixture Depositordepositor@example.orgCrossrefRileyResearcherExample Preprint Labhttps://orcid.org/0000-0006-7890-1234Riley ResearcherCompact preprint fixture01152025Synthetic preprint for CLI e2e tests.https://creativecommons.org/licenses/by/4.0/10.99999/fixture.preprinthttps://doi.curvenote.com/10.99999/fixture.preprint diff --git a/tests/fixtures/journal/article/main.md b/tests/fixtures/journal/article/main.md new file mode 100644 index 0000000..8fba1e8 --- /dev/null +++ b/tests/fixtures/journal/article/main.md @@ -0,0 +1,9 @@ +--- +title: Compact journal fixture article +abstract: | + Synthetic journal abstract for Crossref deposit regression tests. +--- + +# Body + +Journal fixture body with a DOI citation [](doi:10.1145/3290605.3300233). diff --git a/tests/fixtures/journal/article/myst.yml b/tests/fixtures/journal/article/myst.yml new file mode 100644 index 0000000..5e74b35 --- /dev/null +++ b/tests/fixtures/journal/article/myst.yml @@ -0,0 +1,29 @@ +version: 1 +project: + title: Compact journal fixture article + description: Synthetic journal article for CLI e2e tests. + first_page: 10 + last_page: 15 + doi: 10.99999/fixture.journal-article + date: 2025-03-01 + venue: + title: Example Journal of Computing + short_title: ExJ Comp + doi: 10.99999/exjcomp + volume: + number: 12 + issue: + number: 3 + doi: 10.99999/exjcomp.12.3 + authors: + - name: Jordan Journaler + email: jordan@example.org + orcid: 0000-0005-6789-0123 + affiliations: + - Example College + affiliations: + - id: Example College + name: Example College + license: CC-BY-4.0 + toc: + - file: main.md diff --git a/tests/fixtures/preprint/article/main.md b/tests/fixtures/preprint/article/main.md new file mode 100644 index 0000000..a5cee87 --- /dev/null +++ b/tests/fixtures/preprint/article/main.md @@ -0,0 +1,9 @@ +--- +title: Compact preprint fixture +abstract: | + Synthetic preprint abstract for posted-content deposits. +--- + +# Overview + +Preprint fixture body. diff --git a/tests/fixtures/preprint/article/myst.yml b/tests/fixtures/preprint/article/myst.yml new file mode 100644 index 0000000..095865b --- /dev/null +++ b/tests/fixtures/preprint/article/myst.yml @@ -0,0 +1,18 @@ +version: 1 +project: + title: Compact preprint fixture + description: Synthetic preprint for CLI e2e tests. + doi: 10.99999/fixture.preprint + date: 2025-01-15 + authors: + - name: Riley Researcher + email: riley@example.org + orcid: 0000-0006-7890-1234 + affiliations: + - Example Preprint Lab + affiliations: + - id: Example Preprint Lab + name: Example Preprint Lab + license: CC-BY-4.0 + toc: + - file: main.md diff --git a/tests/fixtures/shared/proceedings.yml b/tests/fixtures/shared/proceedings.yml new file mode 100644 index 0000000..3599789 --- /dev/null +++ b/tests/fixtures/shared/proceedings.yml @@ -0,0 +1,39 @@ +version: 1 +project: + open_access: true + license: CC-BY-4.0 + date: 2025-07-10 + venue: + title: Example Science Conference, 2025 + short_title: ExSci + number: 1st + location: Example City + date: July 1 - July 5, 2025 + publisher: Example Proceedings Press + series: Proceedings of the Example Science Conference + issn: 0000-0000 + doi: 10.99999/issn.0000-0000 + volume: + title: Proceedings of the 1st Example Science Conference + subject: Scientific Computing + doi: 10.99999/proc.2025 + editors: + - alex + - blair + contributors: + - id: alex + name: Alex Editor + email: editor@example.org + affiliations: + - Example University + - id: blair + name: Blair Chair + email: chair@example.org + affiliations: + - Example Lab + affiliations: + - id: Example University + name: Example University + ror: https://ror.org/05ggc9x63 + - id: Example Lab + name: Example Lab diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..60b325f --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/**/*.spec.ts'], + exclude: ['tests/**/*.e2e.spec.ts', 'node_modules'], + }, +}); diff --git a/vitest.e2e.config.ts b/vitest.e2e.config.ts new file mode 100644 index 0000000..086cc44 --- /dev/null +++ b/vitest.e2e.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['tests/**/*.e2e.spec.ts'], + testTimeout: 120000, + }, +});