@actuate-media/cms-core 0.28.0 → 0.29.0

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.
Files changed (86) hide show
  1. package/dist/__tests__/diagnostics/env.test.js +18 -0
  2. package/dist/__tests__/diagnostics/env.test.js.map +1 -1
  3. package/dist/__tests__/media/seo.test.d.ts +2 -0
  4. package/dist/__tests__/media/seo.test.d.ts.map +1 -0
  5. package/dist/__tests__/media/seo.test.js +186 -0
  6. package/dist/__tests__/media/seo.test.js.map +1 -0
  7. package/dist/__tests__/redirects/graph.test.d.ts +2 -0
  8. package/dist/__tests__/redirects/graph.test.d.ts.map +1 -0
  9. package/dist/__tests__/redirects/graph.test.js +104 -0
  10. package/dist/__tests__/redirects/graph.test.js.map +1 -0
  11. package/dist/__tests__/redirects/suggest.test.d.ts +2 -0
  12. package/dist/__tests__/redirects/suggest.test.d.ts.map +1 -0
  13. package/dist/__tests__/redirects/suggest.test.js +28 -0
  14. package/dist/__tests__/redirects/suggest.test.js.map +1 -0
  15. package/dist/__tests__/seo/audit-engine.test.d.ts +2 -0
  16. package/dist/__tests__/seo/audit-engine.test.d.ts.map +1 -0
  17. package/dist/__tests__/seo/audit-engine.test.js +152 -0
  18. package/dist/__tests__/seo/audit-engine.test.js.map +1 -0
  19. package/dist/__tests__/seo/config-store.test.js +24 -160
  20. package/dist/__tests__/seo/config-store.test.js.map +1 -1
  21. package/dist/__tests__/seo/score.test.d.ts +2 -0
  22. package/dist/__tests__/seo/score.test.d.ts.map +1 -0
  23. package/dist/__tests__/seo/score.test.js +152 -0
  24. package/dist/__tests__/seo/score.test.js.map +1 -0
  25. package/dist/__tests__/setup/index.test.d.ts +2 -0
  26. package/dist/__tests__/setup/index.test.d.ts.map +1 -0
  27. package/dist/__tests__/setup/index.test.js +82 -0
  28. package/dist/__tests__/setup/index.test.js.map +1 -0
  29. package/dist/api/handlers.d.ts.map +1 -1
  30. package/dist/api/handlers.js +2095 -228
  31. package/dist/api/handlers.js.map +1 -1
  32. package/dist/diagnostics/env.d.ts.map +1 -1
  33. package/dist/diagnostics/env.js +9 -5
  34. package/dist/diagnostics/env.js.map +1 -1
  35. package/dist/index.d.ts +4 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +4 -0
  38. package/dist/index.js.map +1 -1
  39. package/dist/media/index.d.ts +2 -0
  40. package/dist/media/index.d.ts.map +1 -1
  41. package/dist/media/index.js +1 -0
  42. package/dist/media/index.js.map +1 -1
  43. package/dist/media/seo.d.ts +88 -0
  44. package/dist/media/seo.d.ts.map +1 -0
  45. package/dist/media/seo.js +215 -0
  46. package/dist/media/seo.js.map +1 -0
  47. package/dist/redirects/graph.d.ts +68 -0
  48. package/dist/redirects/graph.d.ts.map +1 -0
  49. package/dist/redirects/graph.js +136 -0
  50. package/dist/redirects/graph.js.map +1 -0
  51. package/dist/redirects/index.d.ts +5 -0
  52. package/dist/redirects/index.d.ts.map +1 -0
  53. package/dist/redirects/index.js +3 -0
  54. package/dist/redirects/index.js.map +1 -0
  55. package/dist/redirects/suggest.d.ts +21 -0
  56. package/dist/redirects/suggest.d.ts.map +1 -0
  57. package/dist/redirects/suggest.js +55 -0
  58. package/dist/redirects/suggest.js.map +1 -0
  59. package/dist/seo/audit-engine.d.ts +119 -0
  60. package/dist/seo/audit-engine.d.ts.map +1 -0
  61. package/dist/seo/audit-engine.js +402 -0
  62. package/dist/seo/audit-engine.js.map +1 -0
  63. package/dist/seo/audit-runner.d.ts +41 -0
  64. package/dist/seo/audit-runner.d.ts.map +1 -0
  65. package/dist/seo/audit-runner.js +223 -0
  66. package/dist/seo/audit-runner.js.map +1 -0
  67. package/dist/seo/config-store.d.ts +28 -0
  68. package/dist/seo/config-store.d.ts.map +1 -1
  69. package/dist/seo/config-store.js +22 -0
  70. package/dist/seo/config-store.js.map +1 -1
  71. package/dist/seo/index.d.ts +7 -2
  72. package/dist/seo/index.d.ts.map +1 -1
  73. package/dist/seo/index.js +4 -1
  74. package/dist/seo/index.js.map +1 -1
  75. package/dist/seo/score.d.ts +128 -0
  76. package/dist/seo/score.d.ts.map +1 -0
  77. package/dist/seo/score.js +213 -0
  78. package/dist/seo/score.js.map +1 -0
  79. package/dist/setup/index.d.ts +16 -1
  80. package/dist/setup/index.d.ts.map +1 -1
  81. package/dist/setup/index.js +52 -2
  82. package/dist/setup/index.js.map +1 -1
  83. package/package.json +1 -1
  84. package/prisma/migrations/0008_media_center/migration.sql +47 -0
  85. package/prisma/migrations/0009_seo/migration.sql +143 -0
  86. package/prisma/schema.prisma +183 -8
@@ -1,167 +1,31 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { SEO_CONFIG_COLLECTION, applySeoOverrides, getSeoOverrides, mergeCollectionSeoConfig, mergeSiteSeoConfig, putSeoOverrides, } from '../../seo/config-store.js';
3
- // In-memory mock that mirrors the parts of the Prisma `document` model the
4
- // config-store touches. Using a single-row store keeps the slug-uniqueness
5
- // contract honest — putSeoOverrides should upsert, not duplicate.
6
- function makeDb() {
7
- let row = null;
8
- return {
9
- document: {
10
- findFirst: async ({ where, select: _select, }) => {
11
- if (!row)
12
- return null;
13
- if (row.collection !== where.collection)
14
- return null;
15
- if (row.deletedAt !== null)
16
- return null;
17
- return row;
18
- },
19
- create: async ({ data }) => {
20
- row = {
21
- id: 'row-1',
22
- collection: data.collection,
23
- data: data.data,
24
- deletedAt: null,
25
- };
26
- return row;
27
- },
28
- update: async ({ where, data }) => {
29
- if (!row || row.id !== where.id)
30
- throw new Error('Row not found');
31
- row = { ...row, data: data.data ?? row.data };
32
- return row;
33
- },
34
- },
35
- };
36
- }
37
- describe('config-store: mergeSiteSeoConfig', () => {
38
- it('returns base verbatim when no override is supplied', () => {
39
- const result = mergeSiteSeoConfig({ siteUrl: 'https://a.com', siteName: 'A' }, undefined);
40
- expect(result).toEqual({ siteUrl: 'https://a.com', siteName: 'A' });
41
- });
42
- it('lets override fields win and ignores empty strings', () => {
43
- const result = mergeSiteSeoConfig({ siteUrl: 'https://a.com', siteName: 'A', twitterHandle: '@a' }, { siteUrl: 'https://b.com', siteName: '', twitterHandle: undefined });
44
- // siteUrl flips; empty-string siteName is treated as "unset" (keeps base);
45
- // undefined twitterHandle keeps base too.
46
- expect(result.siteUrl).toBe('https://b.com');
47
- expect(result.siteName).toBe('A');
48
- expect(result.twitterHandle).toBe('@a');
49
- });
50
- it('deep-merges nested robots/sitemap/ogImage blocks', () => {
51
- const result = mergeSiteSeoConfig({
52
- siteUrl: 'https://a.com',
53
- robots: { blockAIBots: false, disabled: false },
54
- sitemap: { defaultChangeFreq: 'weekly', defaultPriority: 0.6 },
55
- ogImage: { theme: 'light' },
56
- }, {
57
- robots: { blockAIBots: true }, // disabled untouched
58
- sitemap: { defaultPriority: 0.9 }, // defaultChangeFreq untouched
59
- ogImage: { theme: 'dark' },
1
+ import { describe, it, expect } from 'vitest';
2
+ import { DEFAULT_SEO_MODULE_SETTINGS, resolveSeoModuleSettings } from '../../seo/config-store.js';
3
+ describe('resolveSeoModuleSettings', () => {
4
+ it('returns defaults when no overrides exist', () => {
5
+ expect(resolveSeoModuleSettings(null)).toEqual(DEFAULT_SEO_MODULE_SETTINGS);
6
+ expect(resolveSeoModuleSettings({})).toEqual(DEFAULT_SEO_MODULE_SETTINGS);
7
+ });
8
+ it('merges stored module settings over defaults', () => {
9
+ const result = resolveSeoModuleSettings({
10
+ module: { indexEntireSite: false, noindexTagPages: true },
60
11
  });
61
- expect(result.robots).toEqual({ blockAIBots: true, disabled: false });
62
- expect(result.sitemap).toEqual({ defaultChangeFreq: 'weekly', defaultPriority: 0.9 });
63
- expect(result.ogImage).toEqual({ theme: 'dark' });
64
- });
65
- });
66
- describe('config-store: mergeCollectionSeoConfig', () => {
67
- it('overrides scalar fields and merges defaultRobots field-by-field', () => {
68
- const result = mergeCollectionSeoConfig({
69
- defaultSchemaType: 'WebPage',
70
- archivePath: '/blog',
71
- defaultRobots: { noIndex: false, noFollow: false },
72
- }, { defaultSchemaType: 'BlogPosting', defaultRobots: { noIndex: true } });
73
- expect(result.defaultSchemaType).toBe('BlogPosting');
74
- expect(result.archivePath).toBe('/blog');
75
- expect(result.defaultRobots).toEqual({ noIndex: true, noFollow: false });
76
- });
77
- });
78
- describe('config-store: applySeoOverrides', () => {
79
- const baseConfig = {
80
- collections: {
81
- posts: {
82
- slug: 'posts',
83
- labels: { singular: 'Post', plural: 'Posts' },
84
- fields: {},
85
- type: 'post',
86
- seo: { defaultSchemaType: 'BlogPosting', sitemapPriority: 0.6 },
87
- },
88
- pages: {
89
- slug: 'pages',
90
- labels: { singular: 'Page', plural: 'Pages' },
91
- fields: {},
92
- type: 'page',
93
- },
94
- },
95
- seo: { siteUrl: 'https://example.com', siteName: 'Example' },
96
- };
97
- it('returns base when overrides are null', () => {
98
- const merged = applySeoOverrides(baseConfig, null);
99
- expect(merged).toBe(baseConfig);
100
- });
101
- it('applies site-wide and per-collection overrides without mutating the base', () => {
102
- const merged = applySeoOverrides(baseConfig, {
103
- site: { siteName: 'New Brand', twitterHandle: '@new' },
104
- collections: {
105
- posts: { defaultSchemaType: 'NewsArticle', defaultRobots: { noIndex: true } },
106
- pages: { archivePath: '/archive' },
107
- },
12
+ expect(result.indexEntireSite).toBe(false);
13
+ expect(result.noindexTagPages).toBe(true);
14
+ // untouched fields keep defaults
15
+ expect(result.sitemapEnabled).toBe(true);
16
+ expect(result.searchConsoleConnected).toBe(false);
17
+ });
18
+ it('ignores undefined/null stored values', () => {
19
+ const result = resolveSeoModuleSettings({
20
+ module: { robotsTxt: undefined },
108
21
  });
109
- expect(merged?.seo?.siteName).toBe('New Brand');
110
- expect(merged?.seo?.siteUrl).toBe('https://example.com'); // untouched
111
- expect(merged?.seo?.twitterHandle).toBe('@new');
112
- expect(merged?.collections?.posts?.seo?.defaultSchemaType).toBe('NewsArticle');
113
- expect(merged?.collections?.posts?.seo?.sitemapPriority).toBe(0.6); // untouched
114
- expect(merged?.collections?.posts?.seo?.defaultRobots).toEqual({ noIndex: true });
115
- expect(merged?.collections?.pages?.seo?.archivePath).toBe('/archive');
116
- // Base config is untouched.
117
- expect(baseConfig.seo?.siteName).toBe('Example');
118
- expect(baseConfig.collections.posts?.seo?.defaultSchemaType).toBe('BlogPosting');
22
+ expect(result.robotsTxt).toBe('');
119
23
  });
120
- it('passes through unknown collection slugs in overrides without crashing', () => {
121
- const merged = applySeoOverrides(baseConfig, {
122
- collections: { 'not-a-real-collection': { defaultSchemaType: 'Service' } },
24
+ it('preserves an explicit custom robots body', () => {
25
+ const result = resolveSeoModuleSettings({
26
+ module: { robotsTxt: 'User-agent: *\nDisallow: /private/' },
123
27
  });
124
- // The unknown slug is silently ignored because we iterate over base.collections.
125
- expect(Object.keys(merged?.collections ?? {})).toEqual(['posts', 'pages']);
126
- });
127
- });
128
- describe('config-store: getSeoOverrides / putSeoOverrides', () => {
129
- let db;
130
- beforeEach(() => {
131
- db = makeDb();
132
- });
133
- it('returns null when no row exists yet', async () => {
134
- const result = await getSeoOverrides(db);
135
- expect(result).toBeNull();
136
- });
137
- it('persists overrides and roundtrips through getSeoOverrides', async () => {
138
- await putSeoOverrides(db, { site: { siteName: 'X' }, collections: { posts: { defaultSchemaType: 'BlogPosting' } } }, 'user-1');
139
- const result = await getSeoOverrides(db);
140
- expect(result?.site?.siteName).toBe('X');
141
- expect(result?.collections?.posts?.defaultSchemaType).toBe('BlogPosting');
142
- expect(result?.updatedBy).toBe('user-1');
143
- expect(typeof result?.updatedAt).toBe('string');
144
- });
145
- it('upserts (does not duplicate) on a second write', async () => {
146
- await putSeoOverrides(db, { site: { siteName: 'X' } }, 'user-1');
147
- await putSeoOverrides(db, { site: { siteName: 'Y' } }, 'user-2');
148
- const result = await getSeoOverrides(db);
149
- expect(result?.site?.siteName).toBe('Y');
150
- expect(result?.updatedBy).toBe('user-2');
151
- });
152
- it('exports the reserved collection slug so it can never collide with a user collection', () => {
153
- expect(SEO_CONFIG_COLLECTION).toMatch(/^__/);
154
- });
155
- it('returns null instead of throwing when db is unusable', async () => {
156
- const broken = {
157
- document: {
158
- findFirst: async () => {
159
- throw new Error('boom');
160
- },
161
- },
162
- };
163
- const result = await getSeoOverrides(broken);
164
- expect(result).toBeNull();
28
+ expect(result.robotsTxt).toContain('Disallow: /private/');
165
29
  });
166
30
  });
167
31
  //# sourceMappingURL=config-store.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config-store.test.js","sourceRoot":"","sources":["../../../src/__tests__/seo/config-store.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"config-store.test.js","sourceRoot":"","sources":["../../../src/__tests__/seo/config-store.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC7C,OAAO,EAAE,2BAA2B,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAEjG,QAAQ,CAAC,0BAA0B,EAAE,GAAG,EAAE;IACxC,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,wBAAwB,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAA;QAC3E,MAAM,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAA;IAC3E,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACrD,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACtC,MAAM,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE;SAC1D,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC1C,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACzC,iCAAiC;QACjC,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACnD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACtC,MAAM,EAAE,EAAE,SAAS,EAAE,SAA8B,EAAE;SACtD,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACnC,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,MAAM,GAAG,wBAAwB,CAAC;YACtC,MAAM,EAAE,EAAE,SAAS,EAAE,oCAAoC,EAAE;SAC5D,CAAC,CAAA;QACF,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAA;IAC3D,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=score.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/seo/score.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,152 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { calculatePageSeoScore, calculateSiteSeoScore, gradeForSeoScore, countH1, PAGE_SEO_WEIGHTS, SITE_SEO_WEIGHTS, } from '../../seo/score.js';
3
+ const FULL_PAGE = {
4
+ title: 'Best Practices Guide',
5
+ slug: 'best-practices',
6
+ url: '/best-practices',
7
+ content: '<h1>Best Practices</h1>' + 'word '.repeat(400),
8
+ metaTitle: 'Best Practices Guide for Modern Teams in 2026',
9
+ metaDescription: 'A comprehensive, practical guide to modern best practices covering process, tooling, and culture so your team can ship reliably and confidently every single week.',
10
+ focusKeyword: 'best practices',
11
+ canonicalUrl: 'https://example.com/best-practices',
12
+ ogTitle: 'Best Practices Guide',
13
+ ogDescription: 'Practical guide.',
14
+ ogImage: 'https://example.com/og.png',
15
+ structuredDataType: 'Article',
16
+ noindex: false,
17
+ shouldBeIndexable: true,
18
+ brokenInternalLinks: 0,
19
+ };
20
+ describe('weights', () => {
21
+ it('page weights sum to 100', () => {
22
+ const sum = Object.values(PAGE_SEO_WEIGHTS).reduce((a, b) => a + b, 0);
23
+ expect(sum).toBe(100);
24
+ });
25
+ it('site weights sum to 100', () => {
26
+ const sum = Object.values(SITE_SEO_WEIGHTS).reduce((a, b) => a + b, 0);
27
+ expect(sum).toBe(100);
28
+ });
29
+ });
30
+ describe('gradeForSeoScore', () => {
31
+ it('maps score bands to grades', () => {
32
+ expect(gradeForSeoScore(95)).toBe('good');
33
+ expect(gradeForSeoScore(80)).toBe('good');
34
+ expect(gradeForSeoScore(74)).toBe('fair');
35
+ expect(gradeForSeoScore(60)).toBe('fair');
36
+ expect(gradeForSeoScore(45)).toBe('poor');
37
+ expect(gradeForSeoScore(1)).toBe('poor');
38
+ expect(gradeForSeoScore(0)).toBe('critical');
39
+ });
40
+ });
41
+ describe('countH1', () => {
42
+ it('counts h1 tags case-insensitively', () => {
43
+ expect(countH1('<h1>a</h1>')).toBe(1);
44
+ expect(countH1('<H1 class="x">a</H1><h1>b</h1>')).toBe(2);
45
+ expect(countH1('<h2>no</h2>')).toBe(0);
46
+ });
47
+ });
48
+ describe('calculatePageSeoScore', () => {
49
+ it('scores a fully-optimised page near 100', () => {
50
+ const result = calculatePageSeoScore(FULL_PAGE);
51
+ expect(result.score).toBeGreaterThanOrEqual(95);
52
+ expect(result.grade).toBe('good');
53
+ expect(result.checks.every((c) => c.points <= c.max)).toBe(true);
54
+ });
55
+ it('scores an empty page as critical', () => {
56
+ const result = calculatePageSeoScore({});
57
+ expect(result.score).toBeLessThan(40);
58
+ expect(['poor', 'critical']).toContain(result.grade);
59
+ });
60
+ it('fails the meta title check when missing', () => {
61
+ const result = calculatePageSeoScore({ ...FULL_PAGE, metaTitle: '' });
62
+ const check = result.checks.find((c) => c.id === 'meta-title');
63
+ expect(check?.status).toBe('fail');
64
+ expect(check?.points).toBe(0);
65
+ });
66
+ it('fails meta description and lowers the score', () => {
67
+ const withDesc = calculatePageSeoScore(FULL_PAGE).score;
68
+ const noDesc = calculatePageSeoScore({ ...FULL_PAGE, metaDescription: '' }).score;
69
+ expect(noDesc).toBeLessThan(withDesc);
70
+ const check = calculatePageSeoScore({ ...FULL_PAGE, metaDescription: '' }).checks.find((c) => c.id === 'meta-description');
71
+ expect(check?.status).toBe('fail');
72
+ });
73
+ it('flags accidental noindex on indexable content', () => {
74
+ const result = calculatePageSeoScore({ ...FULL_PAGE, noindex: true, shouldBeIndexable: true });
75
+ const check = result.checks.find((c) => c.id === 'indexable');
76
+ expect(check?.status).toBe('fail');
77
+ });
78
+ it('does not penalise noindex when content should not be indexable', () => {
79
+ const result = calculatePageSeoScore({ ...FULL_PAGE, noindex: true, shouldBeIndexable: false });
80
+ const check = result.checks.find((c) => c.id === 'indexable');
81
+ expect(check?.status).toBe('pass');
82
+ });
83
+ it('fails the H1 check when there are zero H1 tags in non-empty content', () => {
84
+ const result = calculatePageSeoScore({ ...FULL_PAGE, content: '<p>no heading</p>' });
85
+ const check = result.checks.find((c) => c.id === 'h1');
86
+ expect(check?.status).toBe('fail');
87
+ });
88
+ it('warns on multiple H1 tags', () => {
89
+ const result = calculatePageSeoScore({ ...FULL_PAGE, content: '<h1>a</h1><h1>b</h1>' });
90
+ const check = result.checks.find((c) => c.id === 'h1');
91
+ expect(check?.status).toBe('warn');
92
+ });
93
+ it('flags broken internal links', () => {
94
+ const result = calculatePageSeoScore({ ...FULL_PAGE, brokenInternalLinks: 3 });
95
+ const check = result.checks.find((c) => c.id === 'internal-links');
96
+ expect(check?.status).toBe('fail');
97
+ expect(check?.message).toContain('3');
98
+ });
99
+ it('uses provided wordCount over content', () => {
100
+ const result = calculatePageSeoScore({ ...FULL_PAGE, content: '<h1>x</h1>', wordCount: 500 });
101
+ const check = result.checks.find((c) => c.id === 'content-length');
102
+ expect(check?.status).toBe('pass');
103
+ });
104
+ });
105
+ describe('calculateSiteSeoScore', () => {
106
+ it('returns 100 when all categories are perfect', () => {
107
+ const result = calculateSiteSeoScore({
108
+ content: 100,
109
+ technical: 100,
110
+ linkRedirect: 100,
111
+ structuredSocial: 100,
112
+ performance: 100,
113
+ });
114
+ expect(result.score).toBe(100);
115
+ expect(result.grade).toBe('good');
116
+ });
117
+ it('applies category weights correctly', () => {
118
+ const result = calculateSiteSeoScore({
119
+ content: 100,
120
+ technical: 0,
121
+ linkRedirect: 0,
122
+ structuredSocial: 0,
123
+ performance: 0,
124
+ });
125
+ // content is 30% of the score
126
+ expect(result.score).toBe(30);
127
+ const contentItem = result.breakdown.find((b) => b.category === 'content');
128
+ expect(contentItem?.contribution).toBe(30);
129
+ });
130
+ it('defaults performance to neutral 70 when omitted', () => {
131
+ const result = calculateSiteSeoScore({
132
+ content: 0,
133
+ technical: 0,
134
+ linkRedirect: 0,
135
+ structuredSocial: 0,
136
+ });
137
+ // performance is 10% weight * 70 = 7
138
+ expect(result.score).toBe(7);
139
+ });
140
+ it('clamps out-of-range sub-scores', () => {
141
+ const result = calculateSiteSeoScore({
142
+ content: 200,
143
+ technical: -50,
144
+ linkRedirect: 50,
145
+ structuredSocial: 50,
146
+ performance: 50,
147
+ });
148
+ expect(result.score).toBeGreaterThanOrEqual(0);
149
+ expect(result.score).toBeLessThanOrEqual(100);
150
+ });
151
+ });
152
+ //# sourceMappingURL=score.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"score.test.js","sourceRoot":"","sources":["../../../src/__tests__/seo/score.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/setup/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { checkSetupRequired, createInitialAdmin, isDatabaseUnavailableError, } from '../../setup/index.js';
3
+ function connectionError(code) {
4
+ const err = new Error(`Can't reach database server`);
5
+ err.code = code;
6
+ return err;
7
+ }
8
+ describe('isDatabaseUnavailableError', () => {
9
+ it('detects Prisma connection codes', () => {
10
+ expect(isDatabaseUnavailableError(connectionError('P1001'))).toBe(true);
11
+ expect(isDatabaseUnavailableError(connectionError('ECONNREFUSED'))).toBe(true);
12
+ });
13
+ it('detects PrismaClientInitializationError by name', () => {
14
+ const err = new Error('init failed');
15
+ err.name = 'PrismaClientInitializationError';
16
+ expect(isDatabaseUnavailableError(err)).toBe(true);
17
+ });
18
+ it('detects connection failures by message', () => {
19
+ expect(isDatabaseUnavailableError(new Error("Can't reach database server at db:5432"))).toBe(true);
20
+ });
21
+ it('does not flag ordinary errors', () => {
22
+ expect(isDatabaseUnavailableError(new Error('something else'))).toBe(false);
23
+ expect(isDatabaseUnavailableError(null)).toBe(false);
24
+ expect(isDatabaseUnavailableError('nope')).toBe(false);
25
+ });
26
+ });
27
+ describe('checkSetupRequired', () => {
28
+ it('reports setupRequired when the users table is empty', async () => {
29
+ const db = { user: { count: vi.fn().mockResolvedValue(0) } };
30
+ await expect(checkSetupRequired(db)).resolves.toEqual({ setupRequired: true, userCount: 0 });
31
+ });
32
+ it('reports not-required when users exist', async () => {
33
+ const db = { user: { count: vi.fn().mockResolvedValue(3) } };
34
+ await expect(checkSetupRequired(db)).resolves.toEqual({ setupRequired: false, userCount: 3 });
35
+ });
36
+ it('rethrows connection failures instead of masking them as setup-required', async () => {
37
+ const db = { user: { count: vi.fn().mockRejectedValue(connectionError('ECONNREFUSED')) } };
38
+ await expect(checkSetupRequired(db)).rejects.toThrow(/reach database/i);
39
+ });
40
+ it('still treats non-connection errors as setup-required', async () => {
41
+ const db = { user: { count: vi.fn().mockRejectedValue(new Error('table missing')) } };
42
+ await expect(checkSetupRequired(db)).resolves.toEqual({ setupRequired: true, userCount: 0 });
43
+ });
44
+ });
45
+ describe('createInitialAdmin', () => {
46
+ const validInput = {
47
+ name: 'Admin',
48
+ email: 'Admin@Example.com',
49
+ password: 'a-very-strong-password',
50
+ };
51
+ it('rejects passwords shorter than 12 characters', async () => {
52
+ const db = {};
53
+ const result = await createInitialAdmin(db, { ...validInput, password: 'short' });
54
+ expect(result).toEqual({ success: false, error: 'Password must be at least 12 characters' });
55
+ });
56
+ it('rethrows connection failures so the route can return a 5xx, not a 400', async () => {
57
+ const db = {
58
+ $transaction: vi.fn().mockRejectedValue(connectionError('P1001')),
59
+ };
60
+ await expect(createInitialAdmin(db, validInput)).rejects.toThrow(/reach database/i);
61
+ });
62
+ it('returns a friendly error when setup was already completed', async () => {
63
+ const db = {
64
+ $transaction: vi.fn(async (fn) => fn({ user: { count: vi.fn().mockResolvedValue(1) } })),
65
+ };
66
+ const result = await createInitialAdmin(db, validInput);
67
+ expect(result.success).toBe(false);
68
+ expect(result.error).toMatch(/already completed/i);
69
+ });
70
+ it('creates the admin (normalizing email) when the database is empty', async () => {
71
+ const create = vi.fn().mockResolvedValue({ id: 'user_1' });
72
+ const db = {
73
+ $transaction: vi.fn(async (fn) => fn({ user: { count: vi.fn().mockResolvedValue(0), create } })),
74
+ };
75
+ const result = await createInitialAdmin(db, validInput);
76
+ expect(result).toEqual({ success: true, userId: 'user_1' });
77
+ expect(create).toHaveBeenCalledWith(expect.objectContaining({
78
+ data: expect.objectContaining({ email: 'admin@example.com', role: 'ADMIN' }),
79
+ }));
80
+ });
81
+ });
82
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../../src/__tests__/setup/index.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AA6gB5C,iFAAiF;AACjF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AA8JD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAiQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA60PzD"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAsoB5C,iFAAiF;AACjF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AA8JD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAkQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4kTzD"}