kempo-media 0.3.2 → 0.3.3
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/publish.yml +26 -1
- package/.github/workflows/test.yml +32 -6
- package/package.json +4 -2
- package/scripts/run-tests.js +47 -0
- package/tests/helpers/db.js +19 -0
- package/tests/helpers/preload.js +10 -0
- package/tests/media-api-contract.node-test.js +4 -1
- package/tests/media-data-layer.node-test.js +7 -0
- package/tests/media-migration.node-test.js +6 -0
|
@@ -26,6 +26,25 @@ jobs:
|
|
|
26
26
|
permissions:
|
|
27
27
|
contents: write
|
|
28
28
|
id-token: write
|
|
29
|
+
# The database-backed suites skip themselves when no database is reachable, which reads as a pass.
|
|
30
|
+
# Publishing used to run without one, so a release could go out while the test workflow was red.
|
|
31
|
+
# This is the same service and schema the test workflow uses, so the two gates agree.
|
|
32
|
+
services:
|
|
33
|
+
postgres:
|
|
34
|
+
image: postgres:16-alpine
|
|
35
|
+
env:
|
|
36
|
+
POSTGRES_USER: kempo
|
|
37
|
+
POSTGRES_PASSWORD: kempo
|
|
38
|
+
POSTGRES_DB: kempo_media_test
|
|
39
|
+
ports:
|
|
40
|
+
- 5432:5432
|
|
41
|
+
options: >-
|
|
42
|
+
--health-cmd pg_isready
|
|
43
|
+
--health-interval 10s
|
|
44
|
+
--health-timeout 5s
|
|
45
|
+
--health-retries 5
|
|
46
|
+
env:
|
|
47
|
+
DATABASE_URL: postgresql://kempo:kempo@localhost:5432/kempo_media_test
|
|
29
48
|
steps:
|
|
30
49
|
- uses: actions/checkout@v4
|
|
31
50
|
with:
|
|
@@ -49,9 +68,15 @@ jobs:
|
|
|
49
68
|
- name: Install the host for the test gate
|
|
50
69
|
run: npm install --no-save kempo@latest kempo-files@latest kempo-thumbs@latest kempo-ui@latest kempo-server@latest
|
|
51
70
|
|
|
52
|
-
- name:
|
|
71
|
+
- name: Apply database schema
|
|
72
|
+
run: npx drizzle-kit push --force
|
|
73
|
+
|
|
74
|
+
- name: Run unit tests
|
|
53
75
|
run: npm test
|
|
54
76
|
|
|
77
|
+
- name: Run integration tests
|
|
78
|
+
run: npm run test:integration
|
|
79
|
+
|
|
55
80
|
- name: Set version type
|
|
56
81
|
id: version
|
|
57
82
|
run: |
|
|
@@ -11,9 +11,30 @@ on:
|
|
|
11
11
|
- master
|
|
12
12
|
|
|
13
13
|
jobs:
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
14
|
+
# Unit tier: an in-memory Postgres (PGlite), so there is no service to start and a database
|
|
15
|
+
# suite cannot skip for want of one.
|
|
16
|
+
unit:
|
|
17
|
+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: '22.x'
|
|
25
|
+
|
|
26
|
+
- run: npm ci
|
|
27
|
+
|
|
28
|
+
# kempo, kempo-files, kempo-thumbs, kempo-ui and kempo-server are peer dependencies, which npm ci does not install.
|
|
29
|
+
- name: Install the host for the test gate
|
|
30
|
+
run: npm install --no-save kempo@latest kempo-files@latest kempo-thumbs@latest kempo-ui@latest kempo-server@latest
|
|
31
|
+
|
|
32
|
+
- name: Run unit tests
|
|
33
|
+
run: npm test
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Integration tier: the whole suite on a real Postgres. An unreachable database fails it.
|
|
37
|
+
integration:
|
|
17
38
|
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
|
18
39
|
runs-on: ubuntu-latest
|
|
19
40
|
services:
|
|
@@ -50,8 +71,9 @@ jobs:
|
|
|
50
71
|
- name: Apply database schema
|
|
51
72
|
run: npx drizzle-kit push --force
|
|
52
73
|
|
|
53
|
-
- name: Run
|
|
54
|
-
run: npm test
|
|
74
|
+
- name: Run integration tests
|
|
75
|
+
run: npm run test:integration
|
|
76
|
+
|
|
55
77
|
|
|
56
78
|
# Same suite against the sibling checkout of the host. This extension resolves imports out of
|
|
57
79
|
# kempo, so it breaks whenever the host moves something it reaches into — this job is what
|
|
@@ -129,6 +151,10 @@ jobs:
|
|
|
129
151
|
run: npx drizzle-kit push --force
|
|
130
152
|
working-directory: kempo-media
|
|
131
153
|
|
|
132
|
-
- name: Run
|
|
154
|
+
- name: Run unit tests
|
|
133
155
|
run: npm test
|
|
134
156
|
working-directory: kempo-media
|
|
157
|
+
|
|
158
|
+
- name: Run integration tests
|
|
159
|
+
run: npm run test:integration
|
|
160
|
+
working-directory: kempo-media
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kempo-media",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Media library extension for kempo — upload and manage images, video, audio, 3D models, and archives, stored in kempo-files with thumbnails from kempo-thumbs",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Dustin Poissant",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"main": "sdk.js",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "node scripts/build.js",
|
|
11
|
-
"test": "kempo-test -l minimal",
|
|
11
|
+
"test": "node scripts/run-tests.js unit kempo-test -l minimal",
|
|
12
|
+
"test:integration": "node scripts/run-tests.js integration kempo-test -l minimal",
|
|
12
13
|
"link:local": "npm install ../kempo ../kempo-files ../kempo-thumbs ../kempo-server ../kempo-ui --no-save --no-legacy-peer-deps --force"
|
|
13
14
|
},
|
|
14
15
|
"kempo": {
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
"kempo-ui": ">=0.4.43"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
37
|
+
"@electric-sql/pglite": "^0.5.9",
|
|
36
38
|
"drizzle-kit": "^0.31.10",
|
|
37
39
|
"kempo-testing-framework": "^1.4.11",
|
|
38
40
|
"postgres": "^3.4.9"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import { pathToFileURL } from 'url';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
The two test tiers, then the command to run in each.
|
|
7
|
+
|
|
8
|
+
unit `KEMPO_DB=pglite`: kempo's database module gives an in-process, in-memory Postgres
|
|
9
|
+
(PGlite) holding the tables in drizzle.config.js, so nothing external is needed and a
|
|
10
|
+
database suite can never skip for want of one.
|
|
11
|
+
integration a real Postgres at DATABASE_URL, for the suites that boot a second server process.
|
|
12
|
+
Here an unreachable database fails the run instead of skipping, so it cannot look
|
|
13
|
+
green without having run.
|
|
14
|
+
|
|
15
|
+
`npm test` is the unit tier. `npm run test:integration` runs the whole suite on a real database. CI
|
|
16
|
+
runs both.
|
|
17
|
+
|
|
18
|
+
Usage: node scripts/run-tests.js <unit|integration> <command> [arguments]
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const [tier, command, ...args] = process.argv.slice(2);
|
|
22
|
+
|
|
23
|
+
if(!['unit', 'integration'].includes(tier) || !command){
|
|
24
|
+
console.error('Usage: node scripts/run-tests.js <unit|integration> <command> [arguments]');
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const env = { ...process.env };
|
|
29
|
+
|
|
30
|
+
if(tier === 'unit'){
|
|
31
|
+
env.KEMPO_DB = 'pglite';
|
|
32
|
+
delete env.KEMPO_TEST_TIER;
|
|
33
|
+
// Create the database before any test file can install globals that confuse it. See the preload.
|
|
34
|
+
const preload = pathToFileURL(path.resolve(import.meta.dirname, '..', 'tests', 'helpers', 'preload.js')).href;
|
|
35
|
+
env.NODE_OPTIONS = `${env.NODE_OPTIONS || ''} --import=${preload}`.trim();
|
|
36
|
+
} else {
|
|
37
|
+
delete env.KEMPO_DB;
|
|
38
|
+
env.KEMPO_TEST_TIER = 'integration';
|
|
39
|
+
if(!env.DATABASE_URL){
|
|
40
|
+
console.error('The integration tier needs DATABASE_URL pointing at a throwaway Postgres (named *_test) with the schema applied (npx drizzle-kit push).');
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = spawnSync(command, args, { stdio: 'inherit', env, shell: true });
|
|
46
|
+
|
|
47
|
+
process.exit(result.status ?? 1);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Shared by the database-backed suites. See scripts/run-tests.js for the tiers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const unitTier = process.env.KEMPO_DB === 'pglite';
|
|
6
|
+
export const integrationTier = process.env.KEMPO_TEST_TIER === 'integration';
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
The suite for a file that belongs to the integration tier, reported as such in the unit tier. It is
|
|
10
|
+
explicit rather than a silent skip, and `npm run test:integration` is where it runs.
|
|
11
|
+
*/
|
|
12
|
+
export const integrationOnly = name => ({
|
|
13
|
+
[`${name} (integration tier)`]: async ({ pass }) => pass('runs in `npm run test:integration`: it needs a real Postgres or a second server process'),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
Seeds kempo's system groups, permissions and settings in-process. Needs kempo >= 4.5.5.
|
|
18
|
+
*/
|
|
19
|
+
export const seedDatabase = async () => (await import('kempo/server/utils/setup/seedDatabase.js')).default({ log: () => {} });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Loaded with `--import` by scripts/run-tests.js in the unit tier, before any test file.
|
|
3
|
+
|
|
4
|
+
The in-memory database is created on first import of kempo's database module. Creating it here, in a
|
|
5
|
+
clean process, keeps the run from depending on which suite imports it first or on globals a test
|
|
6
|
+
file has installed by then.
|
|
7
|
+
*/
|
|
8
|
+
if(process.env.KEMPO_DB === 'pglite'){
|
|
9
|
+
await import('kempo/server/db/index.js');
|
|
10
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { unitTier, integrationOnly } from './helpers/db.js';
|
|
1
2
|
import { spawn, execFileSync } from 'child_process';
|
|
2
3
|
import { writeFile, mkdir, rm, readFile, stat } from 'fs/promises';
|
|
3
4
|
import path from 'path';
|
|
@@ -465,6 +466,8 @@ const buildTests = () => {
|
|
|
465
466
|
};
|
|
466
467
|
};
|
|
467
468
|
|
|
468
|
-
export default
|
|
469
|
+
export default unitTier
|
|
470
|
+
? integrationOnly('media API contract')
|
|
471
|
+
: databaseReachable
|
|
469
472
|
? buildTests()
|
|
470
473
|
: skipped('no reachable database — see the "Local development" section of the README (docker compose up -d, then npx drizzle-kit push)');
|
|
@@ -79,6 +79,13 @@ const purge = async () => {
|
|
|
79
79
|
}
|
|
80
80
|
};
|
|
81
81
|
|
|
82
|
+
/*
|
|
83
|
+
kempo-files' media folder also lives on disk, and the disk outlives a database. A fresh in-memory
|
|
84
|
+
database has no row for a folder an earlier run left behind, so the first upload would be refused
|
|
85
|
+
("already exists on disk"). This suite owns that folder, so start from none.
|
|
86
|
+
*/
|
|
87
|
+
await rm(path.join(process.cwd(), 'files', 'media'), { recursive: true, force: true }).catch(() => {});
|
|
88
|
+
|
|
82
89
|
const buildTests = () => ({
|
|
83
90
|
'createMediaAsset records every column and stamps timestamps': async ({ pass, fail }) => {
|
|
84
91
|
const [error, row] = await create({ altText: 'A test image' });
|
|
@@ -78,6 +78,12 @@ const cleanup = async () => {
|
|
|
78
78
|
for(const id of Object.values(IDS)) await deleteFile({ id }).catch(() => {});
|
|
79
79
|
await db.delete(kempoMediaAsset).where(inArray(kempoMediaAsset.id, Object.values(IDS))).catch(() => {});
|
|
80
80
|
await db.delete(settingTable).where(eq(settingTable.name, 'kempo-media:thumbnail_max_dimension')).catch(() => {});
|
|
81
|
+
/*
|
|
82
|
+
kempo-files' folders also live on disk, and the disk outlives a database. A run on a fresh in-memory
|
|
83
|
+
database has no row for the media folder an earlier run left behind, and the migration then refuses
|
|
84
|
+
to create it ("already exists on disk"). This suite owns that folder, so reset it.
|
|
85
|
+
*/
|
|
86
|
+
await rm(path.join(process.cwd(), 'files', 'media'), { recursive: true, force: true }).catch(() => {});
|
|
81
87
|
await removeExtensions();
|
|
82
88
|
await db.execute(sql.raw(`ALTER TABLE "kempoMediaAsset" DROP COLUMN IF EXISTS "filename", DROP COLUMN IF EXISTS "path", DROP COLUMN IF EXISTS "thumbnailPath"`)).catch(() => {});
|
|
83
89
|
if(state.site) await rm(state.site, { recursive: true, force: true }).catch(() => {});
|