paperlint 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +28 -41
- package/.github/workflows/dependabot-automerge.yml +1 -1
- package/CLAUDE.md +51 -56
- package/CONTRIBUTING.md +30 -18
- package/README.md +179 -180
- package/bin/paperlint.test.mjs +45 -0
- package/dist/adapters/node/files.io.d.ts +2 -0
- package/dist/adapters/node/files.io.d.ts.map +1 -1
- package/dist/adapters/node/files.io.js +3 -2
- package/dist/adapters/node/files.io.js.map +1 -1
- package/dist/adapters/pdfjs/fill.d.ts +15 -4
- package/dist/adapters/pdfjs/fill.d.ts.map +1 -1
- package/dist/adapters/pdfjs/fill.js +31 -21
- package/dist/adapters/pdfjs/fill.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +14 -5
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/references/pool.d.ts +8 -0
- package/dist/adapters/references/pool.d.ts.map +1 -0
- package/dist/adapters/references/pool.js +19 -0
- package/dist/adapters/references/pool.js.map +1 -0
- package/dist/build-engine.d.ts.map +1 -1
- package/dist/build-engine.js +6 -2
- package/dist/build-engine.js.map +1 -1
- package/dist/build.d.ts +10 -2
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +29 -11
- package/dist/build.js.map +1 -1
- package/dist/cli.d.ts +93 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +296 -80
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +27 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +4 -3
- package/dist/doctor.js.map +1 -1
- package/dist/domain/result.d.ts +0 -2
- package/dist/domain/result.d.ts.map +1 -1
- package/dist/domain/result.js +0 -4
- package/dist/domain/result.js.map +1 -1
- package/dist/engine.d.ts +9 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +27 -0
- package/dist/engine.js.map +1 -1
- package/dist/hooks-settings.d.ts +8 -5
- package/dist/hooks-settings.d.ts.map +1 -1
- package/dist/hooks-settings.js +32 -32
- package/dist/hooks-settings.js.map +1 -1
- package/dist/init.d.ts +8 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +4 -4
- package/dist/init.js.map +1 -1
- package/dist/latex-log.d.ts +0 -2
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +7 -13
- package/dist/latex-log.js.map +1 -1
- package/dist/link-skills.d.ts +15 -2
- package/dist/link-skills.d.ts.map +1 -1
- package/dist/link-skills.js +36 -20
- package/dist/link-skills.js.map +1 -1
- package/dist/new-paper.d.ts +8 -1
- package/dist/new-paper.d.ts.map +1 -1
- package/dist/new-paper.js +50 -15
- package/dist/new-paper.js.map +1 -1
- package/dist/paper-files.d.ts +30 -8
- package/dist/paper-files.d.ts.map +1 -1
- package/dist/paper-files.js +59 -30
- package/dist/paper-files.js.map +1 -1
- package/dist/pdf-facts.d.ts +11 -0
- package/dist/pdf-facts.d.ts.map +1 -1
- package/dist/pdf-facts.js +16 -18
- package/dist/pdf-facts.js.map +1 -1
- package/dist/pdf-geometry.js +1 -1
- package/dist/pdf-geometry.js.map +1 -1
- package/dist/presets.d.ts +2 -0
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +3 -1
- package/dist/presets.js.map +1 -1
- package/dist/references.js +2 -2
- package/dist/references.js.map +1 -1
- package/dist/structure.js +1 -1
- package/dist/structure.js.map +1 -1
- package/dist/tex-requirements.d.ts +8 -0
- package/dist/tex-requirements.d.ts.map +1 -1
- package/dist/tex-requirements.js +6 -5
- package/dist/tex-requirements.js.map +1 -1
- package/dist/toolchain.d.ts +8 -3
- package/dist/toolchain.d.ts.map +1 -1
- package/dist/toolchain.js +26 -10
- package/dist/toolchain.js.map +1 -1
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/venue-rules.d.ts.map +1 -1
- package/dist/venue-rules.js +12 -10
- package/dist/venue-rules.js.map +1 -1
- package/docs/configuration.md +48 -4
- package/docs/prior-art/README.md +1 -1
- package/docs/prior-art/test-tooling.md +7 -30
- package/docs/rules.md +29 -25
- package/docs/skills.md +80 -0
- package/eslint-rules/install-path-literals.harness.mjs +17 -1
- package/eslint-rules/latex-language.harness.mjs +3 -3
- package/eslint-rules/latex-language.mjs +12 -9
- package/eslint-rules/latex-language.test.mjs +200 -0
- package/eslint-rules/paper-research-question.harness.mjs +73 -7
- package/eslint-rules/paper-stages.harness.mjs +9 -14
- package/eslint-rules/paper-stages.test.mjs +135 -0
- package/eslint-rules/paper-typography.mjs +56 -9
- package/eslint-rules/paper-typography.test.mjs +186 -1
- package/eslint-rules/pdf-last-page-balance.harness.mjs +63 -7
- package/eslint-rules/review-frontmatter.mjs +2 -1
- package/eslint-rules/review-frontmatter.schema.json +1 -1
- package/eslint-rules/review-frontmatter.test.mjs +37 -1
- package/eslint-rules/temp-root-realpath.harness.mjs +49 -0
- package/eslint-rules/tex-build.mjs +13 -13
- package/eslint.config.mjs +6 -2
- package/fixtures/toolchain-mirror/catalog.txt +1 -0
- package/fixtures/toolchain-mirror/install-tl +4 -1
- package/fixtures/toolchain-mirror/stub-fmtutil-sys +4 -0
- package/fixtures/toolchain-mirror/stub-tlmgr +15 -0
- package/hooks/hooks.harness.mjs +6 -11
- package/hooks/paper-edit-guard.hook.mjs +2 -2
- package/hooks/paper-edit-guard.test.mjs +61 -0
- package/hooks/paper-skills-nudge.hook.mjs +2 -2
- package/hooks/paper-skills-nudge.test.mjs +38 -0
- package/hooks/paper-status-gates.test.mjs +42 -0
- package/lib/agent-cli-version.harness.mjs +7 -0
- package/lib/check.d.mts +17 -0
- package/lib/check.mjs +50 -0
- package/lib/check.test.ts +67 -0
- package/lib/markdown.mjs +16 -69
- package/lib/markdown.test.mjs +246 -0
- package/lib/paper-config.harness.mjs +28 -9
- package/lib/paper-config.mjs +7 -2
- package/lib/skill-checks.mjs +18 -46
- package/lib/skill-checks.test.mjs +297 -0
- package/lib/skill-corpus.mjs +11 -13
- package/lib/skill-corpus.test.mjs +54 -0
- package/lib/skill-trigger-cases.harness.mjs +57 -4
- package/package.json +6 -5
- package/scripts/check.harness.mjs +4 -10
- package/scripts/check.mjs +45 -42
- package/scripts/check.test.mjs +104 -0
- package/scripts/coverage-config.test.ts +406 -0
- package/scripts/eslint-report-guard.test.mjs +48 -0
- package/scripts/harness-api.frozen.json +0 -2
- package/scripts/layer-legacy-frozen.d.mts +9 -1
- package/scripts/layer-legacy-frozen.mjs +28 -11
- package/scripts/layer-legacy-frozen.test.ts +143 -1
- package/scripts/rules-are-content-only.harness.mjs +3 -11
- package/scripts/rules-are-content-only.mjs +29 -19
- package/scripts/rules-are-content-only.test.mjs +61 -0
- package/scripts/rules-see-files.mjs +11 -9
- package/scripts/rules-see-files.test.mjs +71 -0
- package/scripts/test.mjs +44 -0
- package/scripts/test.test.mjs +52 -0
- package/skills/grade-paper-writing/__snapshots__/prose-lint.test.mjs.snap +57 -0
- package/skills/grade-paper-writing/prose-lint.mjs +31 -12
- package/skills/grade-paper-writing/prose-lint.test.mjs +153 -0
- package/skills/paper-pipeline/grade-paper-writing-ablation.eval.mjs +3 -3
- package/skills/paper-pipeline/scripts/README.md +13 -132
- package/skills/paper-pipeline/scripts/announce.test.mjs +102 -0
- package/skills/paper-pipeline/scripts/artifact-coverage.test.mjs +86 -0
- package/skills/paper-pipeline/scripts/consumer.d.mts +8 -1
- package/skills/paper-pipeline/scripts/consumer.harness.mjs +3 -3
- package/skills/paper-pipeline/scripts/consumer.mjs +13 -25
- package/skills/paper-pipeline/scripts/consumer.test.mjs +113 -0
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +9 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +380 -0
- package/skills/paper-pipeline/scripts/fixtures/scorecard.mjs +71 -0
- package/skills/paper-pipeline/scripts/generated-code.harness.mjs +4 -4
- package/skills/paper-pipeline/scripts/generated-code.mjs +3 -2
- package/skills/paper-pipeline/scripts/generated-code.test.mjs +62 -0
- package/skills/paper-pipeline/scripts/ledger.cli.test.mjs +302 -0
- package/skills/paper-pipeline/scripts/ledger.mjs +1 -1
- package/skills/paper-pipeline/scripts/{ledger.selftest.mjs → ledger.test.mjs} +44 -76
- package/skills/paper-pipeline/scripts/pipeline-check.cli.test.mjs +474 -0
- package/skills/paper-pipeline/scripts/pipeline-check.harness.mjs +2 -63
- package/skills/paper-pipeline/scripts/pipeline-check.mjs +37 -21
- package/skills/paper-pipeline/scripts/pipeline-edges.mjs +2 -2
- package/skills/paper-pipeline/scripts/pipeline-edges.test.mjs +73 -0
- package/skills/paper-pipeline/scripts/population-map.harness.mjs +4 -47
- package/skills/paper-pipeline/scripts/population-map.mjs +12 -2
- package/skills/paper-pipeline/scripts/population-map.test.mjs +154 -0
- package/skills/paper-pipeline/scripts/provenance.harness.mjs +26 -0
- package/skills/paper-pipeline/scripts/round-diff.harness.mjs +1 -2
- package/skills/paper-pipeline/scripts/round-diff.mjs +13 -6
- package/skills/paper-pipeline/scripts/round-diff.test.mjs +159 -0
- package/skills/paper-pipeline/scripts/run-mechanical.mjs +30 -6
- package/skills/paper-pipeline/scripts/run-mechanical.test.mjs +468 -0
- package/skills/paper-pipeline/scripts/status.mjs +2 -2
- package/skills/paper-pipeline/scripts/status.test.mjs +232 -0
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.harness.mjs +2 -2
- package/skills/render-paper/extract-pdf-facts.harness.mjs +56 -7
- package/skills/tighten-paper/__snapshots__/structure.test.mjs.snap +133 -0
- package/skills/tighten-paper/structure.test.mjs +116 -0
- package/skills/verify-citations/SKILL.md +5 -5
- package/skills/verify-citations/SKILL.md.spec.ts +4 -4
- package/skills/verify-citations/scripts/__snapshots__/verify-cites.net.test.mjs.snap +235 -0
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +274 -0
- package/skills/verify-citations/scripts/bib-authors.cli.test.mjs +178 -0
- package/skills/verify-citations/scripts/bib-authors.mjs +2 -1
- package/skills/verify-citations/scripts/verify-cites.cli.test.mjs +160 -0
- package/skills/verify-citations/scripts/verify-cites.mjs +96 -96
- package/skills/verify-citations/scripts/verify-cites.net.test.mjs +287 -0
- package/skills/verify-citations/scripts/verify-cites.test.mjs +149 -5
- package/src/adapters/banal/failure.test.ts +70 -1
- package/src/adapters/banal/index.test.ts +16 -0
- package/src/adapters/banal/output.test.ts +5 -0
- package/src/adapters/curl/download.test.ts +43 -1
- package/src/adapters/memory/index.test.ts +25 -0
- package/src/adapters/node/files.io.ts +3 -2
- package/src/adapters/node/files.test.ts +13 -1
- package/src/adapters/node/process.test.ts +38 -0
- package/src/adapters/pdfjs/fill.test.ts +34 -0
- package/src/adapters/pdfjs/fill.ts +44 -25
- package/src/adapters/references/index.test.ts +228 -0
- package/src/adapters/references/index.ts +21 -6
- package/src/adapters/references/pool.test.ts +31 -0
- package/src/adapters/references/pool.ts +24 -0
- package/src/build-engine.harness.mjs +3 -7
- package/src/build-engine.test.ts +137 -0
- package/src/build-engine.ts +6 -1
- package/src/build.harness.mjs +51 -11
- package/src/build.test.ts +362 -0
- package/src/build.ts +40 -14
- package/src/cli.harness.mjs +12 -19
- package/src/cli.test.ts +584 -0
- package/src/cli.ts +409 -94
- package/src/doctor.harness.mjs +3 -8
- package/src/doctor.test.ts +64 -0
- package/src/doctor.ts +12 -4
- package/src/domain/result.test.ts +4 -0
- package/src/domain/result.ts +0 -5
- package/src/engine.harness.mjs +77 -7
- package/src/engine.ts +35 -0
- package/src/facts-file.test.ts +110 -0
- package/src/hooks-settings.harness.mjs +43 -13
- package/src/hooks-settings.ts +41 -35
- package/src/init.test.ts +308 -5
- package/src/init.ts +4 -4
- package/src/latex-log.harness.mjs +32 -7
- package/src/latex-log.ts +6 -13
- package/src/latex-loop.harness.mjs +8 -7
- package/src/link-skills.harness.mjs +3 -8
- package/src/link-skills.test.ts +105 -0
- package/src/link-skills.ts +75 -21
- package/src/linted-set.test.ts +118 -0
- package/src/new-paper.harness.mjs +5 -7
- package/src/new-paper.test.ts +203 -2
- package/src/new-paper.ts +84 -17
- package/src/paper-files.test.ts +122 -5
- package/src/paper-files.ts +99 -39
- package/src/paper-settings-commands.test.ts +41 -5
- package/src/paper-settings.test.ts +20 -0
- package/src/parse-args.test.ts +48 -0
- package/src/pdf-facts.harness.mjs +3 -6
- package/src/pdf-facts.test.ts +144 -0
- package/src/pdf-facts.ts +17 -19
- package/src/pdf-geometry.harness.mjs +3 -7
- package/src/pdf-geometry.ts +1 -1
- package/src/presets.test.ts +2 -0
- package/src/presets.ts +7 -1
- package/src/references.test.ts +97 -0
- package/src/references.ts +2 -2
- package/src/rules-config.harness.mjs +10 -8
- package/src/rules-off.test.ts +111 -0
- package/src/structure.harness.mjs +5 -9
- package/src/structure.ts +1 -1
- package/src/tex-requirements.harness.mjs +34 -7
- package/src/tex-requirements.ts +12 -6
- package/src/toolchain.harness.mjs +44 -13
- package/src/toolchain.test.ts +280 -0
- package/src/toolchain.ts +37 -12
- package/src/types.ts +6 -0
- package/src/venue-rules.test.ts +63 -0
- package/src/venue-rules.ts +14 -12
- package/templates/paper/paperlint.json +1 -1
- package/action.mutations.mjs +0 -162
- package/eslint-rules/install-path-literals.mutations.mjs +0 -62
- package/eslint-rules/latex-language.mutations.mjs +0 -196
- package/eslint-rules/paper-research-question.mutations.mjs +0 -127
- package/eslint-rules/paper-stages.mutations.mjs +0 -81
- package/eslint-rules/papers.mutations.mjs +0 -186
- package/eslint-rules/temp-root-realpath.mutations.mjs +0 -99
- package/eslint-rules/tex-build.mutations.mjs +0 -258
- package/hooks/hooks.mutations.mjs +0 -337
- package/lib/agent-cli-version.mutations.mjs +0 -109
- package/lib/mutation-driver.harness.mjs +0 -227
- package/lib/mutation-driver.mjs +0 -397
- package/lib/mutation-driver.mutations.mjs +0 -68
- package/lib/paper-config.mutations.mjs +0 -143
- package/lib/skill-trigger-cases.mutations.mjs +0 -65
- package/scripts/check.mutations.mjs +0 -110
- package/scripts/exclusive.mjs +0 -138
- package/scripts/mutation-batteries-frozen.harness.mjs +0 -204
- package/scripts/mutation-batteries-frozen.mjs +0 -238
- package/scripts/mutation-batteries.frozen.json +0 -108
- package/scripts/rules-are-content-only.mutations.mjs +0 -81
- package/scripts/rules-see-files.mutations.mjs +0 -131
- package/scripts/run-mutations.mjs +0 -100
- package/skills/paper-pipeline/scripts/artifact-coverage.mutations.mjs +0 -218
- package/skills/paper-pipeline/scripts/consumer.mutations.mjs +0 -190
- package/skills/paper-pipeline/scripts/extract-ref-facts.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/generated-code.mutations.mjs +0 -254
- package/skills/paper-pipeline/scripts/pipeline-check.mutations.mjs +0 -54
- package/skills/paper-pipeline/scripts/population-map.mutations.mjs +0 -65
- package/skills/paper-pipeline/scripts/population-map.selftest.mjs +0 -122
- package/skills/paper-pipeline/scripts/provenance.mutations.mjs +0 -59
- package/skills/paper-pipeline/scripts/round-diff.mutations.mjs +0 -276
- package/skills/plan-paper-timeline/plan-paper-timeline.effects.mutations.mjs +0 -65
- package/skills/skill-contract.mutations.mjs +0 -179
- package/src/build.mutations.mjs +0 -196
- package/src/cli.mutations.mjs +0 -414
- package/src/doctor.mutations.mjs +0 -175
- package/src/hooks-settings.mutations.mjs +0 -99
- package/src/link-skills.mutations.mjs +0 -140
- package/src/new-paper.mutations.mjs +0 -79
- package/src/structure.mutations.mjs +0 -83
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`CVEs: found by count, found by list, not found, NVD down, and a malformed id 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"calls": [
|
|
6
|
+
"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2024-0001",
|
|
7
|
+
"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2024-0002",
|
|
8
|
+
"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2024-0003",
|
|
9
|
+
"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2024-0004",
|
|
10
|
+
"https://services.nvd.nist.gov/rest/json/cves/2.0?cveId=CVE-2024-0005",
|
|
11
|
+
],
|
|
12
|
+
"results": [
|
|
13
|
+
{
|
|
14
|
+
"id": "cve-2024-0001",
|
|
15
|
+
"matched_db": "nvd",
|
|
16
|
+
"matched_title": "cve-2024-0001",
|
|
17
|
+
"reason": "confirmed by nvd — "cve-2024-0001"",
|
|
18
|
+
"verdict": "true",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "CVE-2024-0002",
|
|
22
|
+
"matched_db": "nvd",
|
|
23
|
+
"matched_title": "CVE-2024-0002",
|
|
24
|
+
"reason": "confirmed by nvd — "CVE-2024-0002"",
|
|
25
|
+
"verdict": "true",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "CVE-2024-0003",
|
|
29
|
+
"matched_db": null,
|
|
30
|
+
"reason": "RESOLVED_TO_NOTHING: the provided CVE CVE-2024-0003 resolves to no record (checked nvd) — fabrication evidence",
|
|
31
|
+
"verdict": "false",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "CVE-2024-0004",
|
|
35
|
+
"matched_db": null,
|
|
36
|
+
"reason": "all databases unreachable (network/rate-limit) — degraded to unresolvable, never to false",
|
|
37
|
+
"verdict": "unresolvable",
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "CVE-2024-0005",
|
|
41
|
+
"matched_db": null,
|
|
42
|
+
"reason": "RESOLVED_TO_NOTHING: the provided CVE CVE-2024-0005 resolves to no record (checked nvd) — fabrication evidence",
|
|
43
|
+
"verdict": "false",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "CVE-24-1",
|
|
47
|
+
"matched_db": null,
|
|
48
|
+
"reason": "RESOLVED_TO_NOTHING: the provided CVE CVE-24-1 resolves to no record (checked nvd) — fabrication evidence",
|
|
49
|
+
"verdict": "false",
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
}
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
exports[`S2 and arXiv unreachable for an arXiv id 1`] = `
|
|
56
|
+
{
|
|
57
|
+
"calls": [
|
|
58
|
+
"https://api.semanticscholar.org/graph/v1/paper/arXiv%3A1706.03762?fields=title,year",
|
|
59
|
+
"https://export.arxiv.org/api/query?id_list=1706.03762&max_results=1",
|
|
60
|
+
],
|
|
61
|
+
"result": {
|
|
62
|
+
"id": "x",
|
|
63
|
+
"matched_db": null,
|
|
64
|
+
"reason": "all databases unreachable (network/rate-limit) — degraded to unresolvable, never to false",
|
|
65
|
+
"verdict": "unresolvable",
|
|
66
|
+
},
|
|
67
|
+
}
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
exports[`a DOI every registry resolves to the claimed paper, and the authority knows: true 1`] = `
|
|
71
|
+
{
|
|
72
|
+
"calls": [
|
|
73
|
+
"https://api.crossref.org/works/10.1%2Fx",
|
|
74
|
+
"https://api.openalex.org/works/doi:10.1%2Fx",
|
|
75
|
+
"https://api.semanticscholar.org/graph/v1/paper/DOI%3A10.1%2Fx?fields=title,year",
|
|
76
|
+
"https://doi.org/api/handles/10.1/x",
|
|
77
|
+
],
|
|
78
|
+
"result": {
|
|
79
|
+
"id": "v",
|
|
80
|
+
"matched_db": "crossref",
|
|
81
|
+
"matched_title": "Attention Is All You Need",
|
|
82
|
+
"reason": "confirmed by crossref — "Attention Is All You Need"",
|
|
83
|
+
"verdict": "true",
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
exports[`a DOI nobody knows, and the authority says it does not exist: false 1`] = `
|
|
89
|
+
{
|
|
90
|
+
"calls": [
|
|
91
|
+
"https://api.crossref.org/works/10.1%2Fnope",
|
|
92
|
+
"https://api.openalex.org/works/doi:10.1%2Fnope",
|
|
93
|
+
"https://api.semanticscholar.org/graph/v1/paper/DOI%3A10.1%2Fnope?fields=title,year",
|
|
94
|
+
"https://doi.org/api/handles/10.1/nope",
|
|
95
|
+
],
|
|
96
|
+
"result": {
|
|
97
|
+
"id": "f",
|
|
98
|
+
"matched_db": null,
|
|
99
|
+
"reason": "RESOLVED_TO_NOTHING: DOI 10.1/nope does not resolve at the DOI authority (doi.org handle responseCode 100 = not found) — fabrication evidence",
|
|
100
|
+
"verdict": "false",
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
exports[`a title only: every registry's search endpoint, hits and all 1`] = `
|
|
106
|
+
{
|
|
107
|
+
"calls": [
|
|
108
|
+
"https://api.crossref.org/works?query.bibliographic=Attention%20Is%20All%20You%20Need&rows=5",
|
|
109
|
+
"https://api.openalex.org/works?search=Attention%20Is%20All%20You%20Need&per-page=5",
|
|
110
|
+
"https://api.semanticscholar.org/graph/v1/paper/search?query=Attention%20Is%20All%20You%20Need&fields=title,year&limit=5",
|
|
111
|
+
"https://export.arxiv.org/api/query?search_query=ti:%22Attention%20Is%20All%20You%20Need%22&max_results=5",
|
|
112
|
+
],
|
|
113
|
+
"result": {
|
|
114
|
+
"id": "t",
|
|
115
|
+
"matched_db": "crossref",
|
|
116
|
+
"matched_title": "Attention Is All You Need",
|
|
117
|
+
"reason": "confirmed by crossref — "Attention Is All You Need"",
|
|
118
|
+
"verdict": "true",
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
|
|
123
|
+
exports[`a title search that cannot reach any registry is unresolvable 1`] = `
|
|
124
|
+
{
|
|
125
|
+
"calls": [
|
|
126
|
+
"https://api.crossref.org/works?query.bibliographic=Attention%20Is%20All%20You%20Need&rows=5",
|
|
127
|
+
"https://api.openalex.org/works?search=Attention%20Is%20All%20You%20Need&per-page=5",
|
|
128
|
+
"https://api.semanticscholar.org/graph/v1/paper/search?query=Attention%20Is%20All%20You%20Need&fields=title,year&limit=5",
|
|
129
|
+
"https://export.arxiv.org/api/query?search_query=ti:%22Attention%20Is%20All%20You%20Need%22&max_results=5",
|
|
130
|
+
],
|
|
131
|
+
"result": {
|
|
132
|
+
"id": "d",
|
|
133
|
+
"matched_db": null,
|
|
134
|
+
"reason": "all databases unreachable (network/rate-limit) — degraded to unresolvable, never to false",
|
|
135
|
+
"verdict": "unresolvable",
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
exports[`a title search whose answers are empty or shapeless is a miss, not a match 1`] = `
|
|
141
|
+
{
|
|
142
|
+
"calls": [
|
|
143
|
+
"https://api.crossref.org/works?query.bibliographic=Attention%20Is%20All%20You%20Need&rows=5",
|
|
144
|
+
"https://api.openalex.org/works?search=Attention%20Is%20All%20You%20Need&per-page=5",
|
|
145
|
+
"https://api.semanticscholar.org/graph/v1/paper/search?query=Attention%20Is%20All%20You%20Need&fields=title,year&limit=5",
|
|
146
|
+
"https://export.arxiv.org/api/query?search_query=ti:%22Attention%20Is%20All%20You%20Need%22&max_results=5",
|
|
147
|
+
],
|
|
148
|
+
"result": {
|
|
149
|
+
"id": "e",
|
|
150
|
+
"matched_db": null,
|
|
151
|
+
"reason": "not found by title in any database (no resolvable id to disprove) — could be a legit unindexed/regional/pre-digital work, NOT fabrication",
|
|
152
|
+
"verdict": "unresolvable",
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
`;
|
|
156
|
+
|
|
157
|
+
exports[`an arXiv id nobody has: the arXiv API's empty feed and S2's 404 1`] = `
|
|
158
|
+
{
|
|
159
|
+
"calls": [
|
|
160
|
+
"https://api.semanticscholar.org/graph/v1/paper/arXiv%3A9999.99999?fields=title,year",
|
|
161
|
+
"https://export.arxiv.org/api/query?id_list=9999.99999&max_results=1",
|
|
162
|
+
],
|
|
163
|
+
"result": {
|
|
164
|
+
"id": "n",
|
|
165
|
+
"matched_db": null,
|
|
166
|
+
"reason": "RESOLVED_TO_NOTHING: arXiv id 9999.99999 returns a definitive not-found from the reachable arXiv API — fabrication evidence",
|
|
167
|
+
"verdict": "false",
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
`;
|
|
171
|
+
|
|
172
|
+
exports[`an arXiv id: Semantic Scholar by arXiv id, the arXiv API by id_list 1`] = `
|
|
173
|
+
{
|
|
174
|
+
"calls": [
|
|
175
|
+
"https://api.crossref.org/works?query.bibliographic=Attention%20Is%20All%20You%20Need&rows=5",
|
|
176
|
+
"https://api.openalex.org/works?search=Attention%20Is%20All%20You%20Need&per-page=5",
|
|
177
|
+
"https://api.semanticscholar.org/graph/v1/paper/arXiv%3A1706.03762?fields=title,year",
|
|
178
|
+
"https://export.arxiv.org/api/query?id_list=1706.03762&max_results=1",
|
|
179
|
+
],
|
|
180
|
+
"result": {
|
|
181
|
+
"id": "a",
|
|
182
|
+
"matched_db": "semantic_scholar",
|
|
183
|
+
"matched_title": "Attention Is All You Need",
|
|
184
|
+
"reason": "confirmed by semantic_scholar — "Attention Is All You Need"",
|
|
185
|
+
"verdict": "true",
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
exports[`every registry down (429, 503, 403, a thrown error): unresolvable, never false 1`] = `
|
|
191
|
+
{
|
|
192
|
+
"calls": [
|
|
193
|
+
"https://api.crossref.org/works/10.1%2Fx",
|
|
194
|
+
"https://api.openalex.org/works/doi:10.1%2Fx",
|
|
195
|
+
"https://api.semanticscholar.org/graph/v1/paper/DOI%3A10.1%2Fx?fields=title,year",
|
|
196
|
+
"https://doi.org/api/handles/10.1/x",
|
|
197
|
+
],
|
|
198
|
+
"result": {
|
|
199
|
+
"id": "u",
|
|
200
|
+
"matched_db": null,
|
|
201
|
+
"reason": "all databases unreachable (network/rate-limit) — degraded to unresolvable, never to false",
|
|
202
|
+
"verdict": "unresolvable",
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
|
|
207
|
+
exports[`offline: no request at all, the verdict from the citation alone 1`] = `
|
|
208
|
+
{
|
|
209
|
+
"calls": [],
|
|
210
|
+
"result": {
|
|
211
|
+
"id": "o",
|
|
212
|
+
"matched_db": null,
|
|
213
|
+
"reason": "all databases unreachable (network/rate-limit) — degraded to unresolvable, never to false",
|
|
214
|
+
"verdict": "unresolvable",
|
|
215
|
+
},
|
|
216
|
+
}
|
|
217
|
+
`;
|
|
218
|
+
|
|
219
|
+
exports[`records with fields missing: no title, a subtitle, a year only in \`published\`, no id 1`] = `
|
|
220
|
+
{
|
|
221
|
+
"calls": [
|
|
222
|
+
"https://api.crossref.org/works/10.1%2Fx",
|
|
223
|
+
"https://api.openalex.org/works/doi:10.1%2Fx",
|
|
224
|
+
"https://api.semanticscholar.org/graph/v1/paper/DOI%3A10.1%2Fx?fields=title,year",
|
|
225
|
+
"https://doi.org/api/handles/10.1/x",
|
|
226
|
+
],
|
|
227
|
+
"result": {
|
|
228
|
+
"id": "m",
|
|
229
|
+
"matched_db": "crossref",
|
|
230
|
+
"matched_title": "",
|
|
231
|
+
"reason": "confirmed by crossref",
|
|
232
|
+
"verdict": "true",
|
|
233
|
+
},
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bib-authors.mjs's comparison (`checkAuthors`) and its reading of DBLP's answer (`dblpHits`).
|
|
3
|
+
*
|
|
4
|
+
* `checkAuthors` takes its lookup and its pause as parameters for exactly this: the buckets it
|
|
5
|
+
* sorts entries into — finding, matched, not applicable, NOT CHECKED — are our logic, and each is
|
|
6
|
+
* driven here with a lookup that answers on purpose. `dblpHits` is our parser of DBLP's search
|
|
7
|
+
* JSON; it is fed that documented shape (`result.hits.hit[].info`), which pins how WE read it,
|
|
8
|
+
* not how DBLP behaves — the colocated bib-authors.test.mjs keeps the live service out of tests.
|
|
9
|
+
*/
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { afterEach, test, vi } from "vitest";
|
|
12
|
+
import {
|
|
13
|
+
checkAuthors,
|
|
14
|
+
claimsPublished,
|
|
15
|
+
dblpHits,
|
|
16
|
+
parseBib,
|
|
17
|
+
surnames,
|
|
18
|
+
} from "./bib-authors.mjs";
|
|
19
|
+
|
|
20
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
21
|
+
|
|
22
|
+
const entry = (key, author, title, booktitle = "Proc. X") => ({
|
|
23
|
+
type: "inproceedings",
|
|
24
|
+
key,
|
|
25
|
+
author,
|
|
26
|
+
title,
|
|
27
|
+
booktitle,
|
|
28
|
+
journal: "",
|
|
29
|
+
});
|
|
30
|
+
const hit = (
|
|
31
|
+
title,
|
|
32
|
+
authors,
|
|
33
|
+
venue = "X",
|
|
34
|
+
type = "Conference and Workshop Papers",
|
|
35
|
+
) => ({
|
|
36
|
+
title,
|
|
37
|
+
authors,
|
|
38
|
+
venue,
|
|
39
|
+
year: "2024",
|
|
40
|
+
type,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("checkAuthors sorts every kind of entry into its bucket", async () => {
|
|
44
|
+
const parsed = [
|
|
45
|
+
entry("same", "Ada Lovelace and Alan Turing", "Same Paper"),
|
|
46
|
+
entry("dropped", "Ada Lovelace", "Dropped Author"),
|
|
47
|
+
entry("order", "Alan Turing and Ada Lovelace", "Order Swapped"),
|
|
48
|
+
entry("pre", "Ada Lovelace", "A Preprint", "arXiv preprint"),
|
|
49
|
+
entry("etal", "Ada Lovelace and others", "Truncated"),
|
|
50
|
+
entry("nomatch", "Ada Lovelace", "Nobody Has This"),
|
|
51
|
+
entry("onlycorr", "Ada Lovelace", "Only On CoRR"),
|
|
52
|
+
entry("down", "Ada Lovelace", "Service Down"),
|
|
53
|
+
{
|
|
54
|
+
type: "mdref",
|
|
55
|
+
key: "sw",
|
|
56
|
+
unparsed: true,
|
|
57
|
+
author: "",
|
|
58
|
+
title: "",
|
|
59
|
+
booktitle: "",
|
|
60
|
+
journal: "",
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
const answers = {
|
|
64
|
+
"Same Paper": [hit("Same Paper.", ["Ada Lovelace", "Alan Turing"])],
|
|
65
|
+
"Dropped Author": [hit("Dropped Author", ["Ada Lovelace", "Alan Turing"])],
|
|
66
|
+
"Order Swapped": [hit("Order Swapped", ["Ada Lovelace", "Alan Turing"])],
|
|
67
|
+
"Nobody Has This": [hit("Something Else", ["X"])],
|
|
68
|
+
"Only On CoRR": [
|
|
69
|
+
hit("Only On CoRR", ["Ada Lovelace"], "CoRR", "Informal Publications"),
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
const pauses = [];
|
|
73
|
+
const result = await checkAuthors(parsed, {
|
|
74
|
+
lookup: async (title) => {
|
|
75
|
+
if (title === "Service Down") throw new Error("DBLP 500");
|
|
76
|
+
return answers[title];
|
|
77
|
+
},
|
|
78
|
+
pause: (ms) => pauses.push(ms),
|
|
79
|
+
});
|
|
80
|
+
assert.deepEqual(
|
|
81
|
+
{ ...result, pauses },
|
|
82
|
+
{
|
|
83
|
+
findings: [
|
|
84
|
+
{
|
|
85
|
+
key: "dropped",
|
|
86
|
+
venue: "X 2024",
|
|
87
|
+
ours: ["lovelace"],
|
|
88
|
+
theirs: ["lovelace", "turing"],
|
|
89
|
+
missing: ["turing"],
|
|
90
|
+
extra: [],
|
|
91
|
+
orderDiffers: false,
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "order",
|
|
95
|
+
venue: "X 2024",
|
|
96
|
+
ours: ["turing", "lovelace"],
|
|
97
|
+
theirs: ["lovelace", "turing"],
|
|
98
|
+
missing: [],
|
|
99
|
+
extra: [],
|
|
100
|
+
orderDiffers: true,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
skipped: [
|
|
104
|
+
{
|
|
105
|
+
key: "sw",
|
|
106
|
+
why: "no author/title — a reference to software or a dataset, DBLP does not apply",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "etal",
|
|
110
|
+
why: "author list ends in `and others` — completeness not checkable",
|
|
111
|
+
},
|
|
112
|
+
{ key: "nomatch", why: "no DBLP title match" },
|
|
113
|
+
{ key: "onlycorr", why: "only a preprint record on DBLP" },
|
|
114
|
+
],
|
|
115
|
+
unchecked: [{ key: "down", why: "DBLP lookup failed: DBLP 500" }],
|
|
116
|
+
matched: ["same"],
|
|
117
|
+
// 900 ms after each compared entry; 1500 and 3000 between the three failed attempts.
|
|
118
|
+
pauses: [900, 900, 900, 1500, 3000],
|
|
119
|
+
},
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("a lookup that fails once and then answers is retried, not reported", async () => {
|
|
124
|
+
let calls = 0;
|
|
125
|
+
const result = await checkAuthors([entry("k", "Ada Lovelace", "Flaky")], {
|
|
126
|
+
lookup: async () => {
|
|
127
|
+
if (calls++ === 0)
|
|
128
|
+
throw Object.assign(new Error("DBLP 429"), { retryable: true });
|
|
129
|
+
return [hit("Flaky", ["Ada Lovelace"])];
|
|
130
|
+
},
|
|
131
|
+
pause: () => {},
|
|
132
|
+
});
|
|
133
|
+
assert.deepEqual(result, {
|
|
134
|
+
findings: [],
|
|
135
|
+
skipped: [],
|
|
136
|
+
unchecked: [],
|
|
137
|
+
matched: ["k"],
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("the default pause really waits", async () => {
|
|
142
|
+
vi.useFakeTimers();
|
|
143
|
+
try {
|
|
144
|
+
const done = checkAuthors([entry("k", "Ada Lovelace", "T")], {
|
|
145
|
+
lookup: async () => [hit("T", ["Ada Lovelace"])],
|
|
146
|
+
});
|
|
147
|
+
await vi.advanceTimersByTimeAsync(900);
|
|
148
|
+
assert.deepEqual((await done).matched, ["k"]);
|
|
149
|
+
} finally {
|
|
150
|
+
vi.useRealTimers();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
/** A stubbed fetch that answers every request with `status` and `body`, recording the URLs. */
|
|
155
|
+
function fakeDblp(status, body) {
|
|
156
|
+
const urls = [];
|
|
157
|
+
vi.stubGlobal("fetch", async (url) => {
|
|
158
|
+
urls.push(url);
|
|
159
|
+
return new Response(body === undefined ? null : JSON.stringify(body), {
|
|
160
|
+
status,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
return urls;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
test("dblpHits reads DBLP's hit list: one author or several, the disambiguation number dropped", async () => {
|
|
167
|
+
const urls = fakeDblp(200, {
|
|
168
|
+
result: {
|
|
169
|
+
hits: {
|
|
170
|
+
hit: [
|
|
171
|
+
{
|
|
172
|
+
info: {
|
|
173
|
+
title: "One",
|
|
174
|
+
venue: "ICSE",
|
|
175
|
+
year: "2024",
|
|
176
|
+
type: "Conference and Workshop Papers",
|
|
177
|
+
authors: { author: { text: "Ada Lovelace 0001" } },
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
info: {
|
|
182
|
+
title: "Two",
|
|
183
|
+
authors: { author: [{ text: "A B" }, { text: "C D" }] },
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{ info: {} },
|
|
187
|
+
{},
|
|
188
|
+
],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
assert.deepEqual(
|
|
193
|
+
{ hits: await dblpHits("a title"), urls },
|
|
194
|
+
{
|
|
195
|
+
hits: [
|
|
196
|
+
{
|
|
197
|
+
venue: "ICSE",
|
|
198
|
+
year: "2024",
|
|
199
|
+
type: "Conference and Workshop Papers",
|
|
200
|
+
title: "One",
|
|
201
|
+
authors: ["Ada Lovelace"],
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
venue: "",
|
|
205
|
+
year: "",
|
|
206
|
+
type: "",
|
|
207
|
+
title: "Two",
|
|
208
|
+
authors: ["A B", "C D"],
|
|
209
|
+
},
|
|
210
|
+
{ venue: "", year: "", type: "", title: "", authors: [] },
|
|
211
|
+
{ venue: "", year: "", type: "", title: "", authors: [] },
|
|
212
|
+
],
|
|
213
|
+
urls: ["https://dblp.org/search/publ/api/?q=a%20title&format=json&h=6"],
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
test("dblpHits: no hits is an empty list; 429 is retryable; other errors are not", async () => {
|
|
219
|
+
fakeDblp(200, {});
|
|
220
|
+
const none = await dblpHits("x");
|
|
221
|
+
fakeDblp(429);
|
|
222
|
+
const limited = await dblpHits("x").catch((e) => [e.message, e.retryable]);
|
|
223
|
+
fakeDblp(500);
|
|
224
|
+
const down = await dblpHits("x").catch((e) => [e.message, e.retryable]);
|
|
225
|
+
assert.deepEqual(
|
|
226
|
+
[none, limited, down],
|
|
227
|
+
[[], ["DBLP 429", true], ["DBLP 500", undefined]],
|
|
228
|
+
);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("parseBib reads quoted and bare field values, and a field that is absent", () => {
|
|
232
|
+
assert.deepEqual(
|
|
233
|
+
parseBib(
|
|
234
|
+
'@article{k, author = "Ada Lovelace", title = {T}, journal = J, }',
|
|
235
|
+
),
|
|
236
|
+
[
|
|
237
|
+
{
|
|
238
|
+
type: "article",
|
|
239
|
+
key: "k",
|
|
240
|
+
author: "Ada Lovelace",
|
|
241
|
+
title: "T",
|
|
242
|
+
booktitle: "",
|
|
243
|
+
journal: "J",
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("parseBib: braces nested inside a value, and a bare last value with no comma after it", () => {
|
|
250
|
+
assert.deepEqual(
|
|
251
|
+
parseBib("@article{k, title = {The {BERT} Model}, journal = J}"),
|
|
252
|
+
[
|
|
253
|
+
{
|
|
254
|
+
type: "article",
|
|
255
|
+
key: "k",
|
|
256
|
+
author: "",
|
|
257
|
+
title: "The {BERT} Model",
|
|
258
|
+
booktitle: "",
|
|
259
|
+
journal: "J",
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("surnames of nothing is nothing; a name with an empty family part contributes nothing", () => {
|
|
266
|
+
assert.deepEqual(
|
|
267
|
+
[surnames(""), surnames(", Ada and Lovelace")],
|
|
268
|
+
[[], ["lovelace"]],
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("an entry naming no venue at all does not claim to be published", () => {
|
|
273
|
+
assert.equal(claimsPublished({ booktitle: "", journal: "" }), false);
|
|
274
|
+
});
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bib-authors.mjs as a process: which file it picks from a paper directory, the three
|
|
3
|
+
* bibliography sources it reads (.bib, a .tex's filecontents or its sibling refs.bib, a markdown
|
|
4
|
+
* reference list), and the report and exit code for each outcome. DBLP is a preload that answers
|
|
5
|
+
* by title from a table (the documented `result.hits.hit[].info` shape) — no network.
|
|
6
|
+
*/
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { dirname } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { test } from "vitest";
|
|
12
|
+
import { runNode, useTempDir, writeTree } from "../../../test/support.mjs";
|
|
13
|
+
|
|
14
|
+
const SCRIPT = join(dirname(fileURLToPath(import.meta.url)), "bib-authors.mjs");
|
|
15
|
+
const root = useTempDir("bib-authors-cli-");
|
|
16
|
+
|
|
17
|
+
const DBLP_ANSWERS = {
|
|
18
|
+
"Dropped Author": [["Ada Lovelace", "Alan Turing"]],
|
|
19
|
+
"Swapped Order": [["Ada Lovelace", "Alan Turing"]],
|
|
20
|
+
"All Good": [["Ada Lovelace"]],
|
|
21
|
+
};
|
|
22
|
+
const bib = (key, author, title, venue = "booktitle = {Proc. X}") =>
|
|
23
|
+
`@inproceedings{${key}, author = {${author}}, title = {${title}}, ${venue}}\n`;
|
|
24
|
+
|
|
25
|
+
writeTree(root, {
|
|
26
|
+
// The retry backoff (1.5 s, then 3 s) and the 900 ms courtesy pause are real sleeps in the
|
|
27
|
+
// script. Under CPU load they made "DBLP down" take 11.7 s once and time out. The preload
|
|
28
|
+
// stands in for the clock too: every timer fires at once, so no test here depends on time.
|
|
29
|
+
"dblp.mjs":
|
|
30
|
+
"const realSetTimeout = globalThis.setTimeout;\n" +
|
|
31
|
+
"globalThis.setTimeout = (fn, _ms, ...args) => realSetTimeout(fn, 0, ...args);\n" +
|
|
32
|
+
`const answers = ${JSON.stringify(DBLP_ANSWERS)};\n` +
|
|
33
|
+
"globalThis.fetch = async (url) => {\n" +
|
|
34
|
+
" const q = new URL(url).searchParams.get('q');\n" +
|
|
35
|
+
" if (q === 'Service Down') return new Response(null, { status: 500 });\n" +
|
|
36
|
+
" const hit = (answers[q] ?? []).map((authors) => ({ info: { title: q, venue: 'X', year: '2024',\n" +
|
|
37
|
+
" type: 'Conference and Workshop Papers', authors: { author: authors.map((text) => ({ text })) } } }));\n" +
|
|
38
|
+
" return new Response(JSON.stringify({ result: { hits: { hit } } }), { status: 200 });\n};\n",
|
|
39
|
+
"findings/paper.bib":
|
|
40
|
+
bib("dropped", "Ada Lovelace and Grace Hopper", "Dropped Author") +
|
|
41
|
+
bib("order", "Alan Turing and Ada Lovelace", "Swapped Order"),
|
|
42
|
+
"findings/other.bib": bib("never", "X", "Never Read"),
|
|
43
|
+
"twotex/b.tex": "\\documentclass{article}\n",
|
|
44
|
+
"twotex/a.tex":
|
|
45
|
+
"\\begin{filecontents*}{refs.bib}\n" +
|
|
46
|
+
bib("pre", "Ada Lovelace", "A Preprint", "journal = {arXiv preprint}") +
|
|
47
|
+
"\\end{filecontents*}\n",
|
|
48
|
+
"sibling/paper.tex": "\\documentclass{article}\n",
|
|
49
|
+
"sibling/refs.bib": bib("good", "Ada Lovelace", "All Good"),
|
|
50
|
+
"nobib/paper.tex": "\\documentclass{article}\n",
|
|
51
|
+
"md/paper.md":
|
|
52
|
+
"# P\n\n## References\n\n1. A. Lovelace. *All Good.* Proc. X, 2024.\n2. some-npm-package, https://npm.example\n",
|
|
53
|
+
"down/refs.bib": bib("down", "Ada Lovelace", "Service Down"),
|
|
54
|
+
"empty/.keep": "",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const run = (...args) => {
|
|
58
|
+
const r = runNode(SCRIPT, args, {
|
|
59
|
+
nodeArgs: ["--import", join(root, "dblp.mjs")],
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
...r,
|
|
63
|
+
stdout: r.stdout.replaceAll(root, "<root>"),
|
|
64
|
+
stderr: r.stderr.replaceAll(root, "<root>"),
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
test("refusals: no argument, no such path, a directory with nothing to read, a .tex with no bibliography", () => {
|
|
69
|
+
assert.deepEqual(
|
|
70
|
+
[
|
|
71
|
+
run(),
|
|
72
|
+
run(join(root, "nope")),
|
|
73
|
+
run(join(root, "empty")),
|
|
74
|
+
run(join(root, "nobib", "paper.tex")),
|
|
75
|
+
].map(({ status, stdout, stderr }) => ({ status, stdout, stderr })),
|
|
76
|
+
[
|
|
77
|
+
{
|
|
78
|
+
status: 2,
|
|
79
|
+
stdout: "",
|
|
80
|
+
stderr:
|
|
81
|
+
"bib-authors: usage: bib-authors.mjs <paper-dir|file.bib|file.tex> [--json]\n",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
status: 2,
|
|
85
|
+
stdout: "",
|
|
86
|
+
stderr: "bib-authors: no such path: <root>/nope\n",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
status: 2,
|
|
90
|
+
stdout: "",
|
|
91
|
+
stderr: "bib-authors: no .bib, .tex or paper.md in <root>/empty\n",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
status: 2,
|
|
95
|
+
stdout: "",
|
|
96
|
+
stderr:
|
|
97
|
+
"bib-authors: no bibliography found in <root>/nobib/paper.tex (no filecontents block, no refs.bib beside it)\n",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("a paper directory: the canonical paper.bib is read, and each difference is printed — FAIL, exit 1", () => {
|
|
104
|
+
assert.deepEqual(run(join(root, "findings")), {
|
|
105
|
+
status: 1,
|
|
106
|
+
stderr: "",
|
|
107
|
+
stdout: [
|
|
108
|
+
"== bib-authors: <root>/findings/paper.bib ==",
|
|
109
|
+
"",
|
|
110
|
+
" 🔴 dropped (DBLP: X 2024)",
|
|
111
|
+
" MISSING from ours : turing",
|
|
112
|
+
" EXTRA in ours : hopper",
|
|
113
|
+
"",
|
|
114
|
+
" 🔴 order (DBLP: X 2024)",
|
|
115
|
+
" ORDER differs",
|
|
116
|
+
" ours : turing > lovelace",
|
|
117
|
+
" DBLP : lovelace > turing",
|
|
118
|
+
"",
|
|
119
|
+
"-- 2 entries · 2 difference(s) · 0 not applicable · 0 NOT CHECKED",
|
|
120
|
+
"FAIL: our author list disagrees with the version we claim to cite.",
|
|
121
|
+
"",
|
|
122
|
+
].join("\n"),
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("two .tex files and no paper.tex: the first by name, said out loud; its filecontents is read", () => {
|
|
127
|
+
const r = run(join(root, "twotex"), "--json");
|
|
128
|
+
assert.deepEqual(
|
|
129
|
+
{ status: r.status, stderr: r.stderr, report: JSON.parse(r.stdout) },
|
|
130
|
+
{
|
|
131
|
+
status: 0,
|
|
132
|
+
stderr:
|
|
133
|
+
"⚠️ <root>/twotex holds several .tex files and no canonical paper.tex: a.tex, b.tex — took a.tex. If that is the wrong one, name the file explicitly.\n",
|
|
134
|
+
report: {
|
|
135
|
+
file: "<root>/twotex/a.tex",
|
|
136
|
+
entries: 1,
|
|
137
|
+
findings: [],
|
|
138
|
+
skipped: [],
|
|
139
|
+
unchecked: [],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
test("a .tex with no filecontents reads the refs.bib beside it — PASS, exit 0", () => {
|
|
146
|
+
const r = run(join(root, "sibling", "paper.tex"));
|
|
147
|
+
assert.deepEqual(r, {
|
|
148
|
+
status: 0,
|
|
149
|
+
stderr: "",
|
|
150
|
+
stdout:
|
|
151
|
+
"== bib-authors: <root>/sibling/refs.bib ==\n\n-- 1 entries · 0 difference(s) · 0 not applicable · 0 NOT CHECKED\nPASS: no author-list disagreement.\n",
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a markdown reference list: a software entry is not applicable, not dropped", () => {
|
|
156
|
+
const r = run(join(root, "md"));
|
|
157
|
+
assert.deepEqual(r, {
|
|
158
|
+
status: 0,
|
|
159
|
+
stderr: "",
|
|
160
|
+
stdout:
|
|
161
|
+
"== bib-authors: <root>/md/paper.md ==\n" +
|
|
162
|
+
" · n/a ref2 — no author/title — a reference to software or a dataset, DBLP does not apply\n\n" +
|
|
163
|
+
"-- 1 entries · 0 difference(s) · 1 not applicable · 0 NOT CHECKED\nPASS: no author-list disagreement.\n",
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("DBLP down: NOT CHECKED, PARTIAL, exit 2 — never a pass", () => {
|
|
168
|
+
const r = run(join(root, "down"));
|
|
169
|
+
assert.deepEqual(r, {
|
|
170
|
+
status: 2,
|
|
171
|
+
stderr: "",
|
|
172
|
+
stdout:
|
|
173
|
+
"== bib-authors: <root>/down/refs.bib ==\n" +
|
|
174
|
+
" ⚠️ NOT CHECKED down — DBLP lookup failed: DBLP 500\n\n" +
|
|
175
|
+
"-- 1 entries · 0 difference(s) · 0 not applicable · 1 NOT CHECKED\n" +
|
|
176
|
+
"PARTIAL: no disagreement among the entries reached, but 1 could not be checked — this is NOT a pass.\n",
|
|
177
|
+
});
|
|
178
|
+
});
|