Skip to content

test: add unit tests for bootstrap and register functionality #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,31 @@ jobs:
with:
name: strapi-plugin-imagekit-package
path: strapi-plugin-imagekit-*.tgz

test:
runs-on: ubuntu-latest
needs: pack
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name

steps:
- name: ⬇️ Check out code
uses: actions/checkout@v4

- name: 🟢 Enable Corepack
run: corepack enable

- name: 🟢 Set up Node 20
uses: actions/setup-node@v4
with:
node-version: 20.x
cache: yarn

- name: 📦 Install deps, build
run: |
yarn install --frozen-lockfile
yarn build
env:
CI: true

- name: 🧪 Run tests
run: yarn test:unit:ci
4 changes: 3 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ jobs:
cache: yarn
registry-url: 'https://registry.npmjs.org'

- name: Build and Publish
- name: Build, Test and Publish
run: |
yarn install --frozen-lockfile

yarn test:unit:ci

yarn build

# print the NPM user name for validation
Expand Down
21 changes: 21 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { JestConfigWithTsJest } from 'ts-jest';
import { defaults as tsjPreset } from 'ts-jest/presets';

const config: JestConfigWithTsJest = {
testMatch: ['**/tests/**/?(*.)+(spec|test).(t|j)s'],
transform: {
...tsjPreset.transform,
},
preset: 'ts-jest',
coverageDirectory: './coverage/',
collectCoverage: true,
reporters: ['default', 'jest-junit'],
collectCoverageFrom: ['server/src/**/*.{js,jsx,ts,tsx}'],
globals: {
'ts-jest': {
tsconfig: './tsconfig.test.json',
},
},
};

export default config;
39 changes: 39 additions & 0 deletions junit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="jest tests" tests="16" failures="0" errors="0" time="0.991">
<testsuite name="Bootstrap" errors="0" failures="0" skipped="0" timestamp="2025-06-02T16:06:29" time="0.289" tests="11">
<testcase classname="Bootstrap saveConfig when config does not exist in store should save default config if no config exists" name="Bootstrap saveConfig when config does not exist in store should save default config if no config exists" time="0.006">
</testcase>
<testcase classname="Bootstrap saveConfig when config exists in store should not overwrite existing config" name="Bootstrap saveConfig when config exists in store should not overwrite existing config" time="0.001">
</testcase>
<testcase classname="Bootstrap addPermissions should register all permissions" name="Bootstrap addPermissions should register all permissions" time="0">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is enabled upload is called from provider" name="Bootstrap registerUploadProvider upload is enabled upload is called from provider" time="0.008">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is enabled uploadStream is called from provider" name="Bootstrap registerUploadProvider upload is enabled uploadStream is called from provider" time="0">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is enabled delete is called from provider" name="Bootstrap registerUploadProvider upload is enabled delete is called from provider" time="0">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is enabled isPrivate is called from provider" name="Bootstrap registerUploadProvider upload is enabled isPrivate is called from provider" time="0">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is disabled upload is called from provider" name="Bootstrap registerUploadProvider upload is disabled upload is called from provider" time="0">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is disabled uploadStream is called from provider" name="Bootstrap registerUploadProvider upload is disabled uploadStream is called from provider" time="0.001">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is disabled delete is called from provider" name="Bootstrap registerUploadProvider upload is disabled delete is called from provider" time="0">
</testcase>
<testcase classname="Bootstrap registerUploadProvider upload is disabled isPrivate is called from provider" name="Bootstrap registerUploadProvider upload is disabled isPrivate is called from provider" time="0">
</testcase>
</testsuite>
<testsuite name="Register" errors="0" failures="0" skipped="0" timestamp="2025-06-02T16:06:29" time="0.698" tests="5">
<testcase classname="Register registerSanitizer sanitizer is registered sanitizer is registered" name="Register registerSanitizer sanitizer is registered sanitizer is registered" time="0.003">
</testcase>
<testcase classname="Register registerSanitizer sanitizer tests should not convert to imagekit url" name="Register registerSanitizer sanitizer tests should not convert to imagekit url" time="0">
</testcase>
<testcase classname="Register registerSanitizer sanitizer tests should not convert to imagekit url" name="Register registerSanitizer sanitizer tests should not convert to imagekit url" time="0.001">
</testcase>
<testcase classname="Register registerSanitizer sanitizer tests should convert to imagekit url" name="Register registerSanitizer sanitizer tests should convert to imagekit url" time="0.002">
</testcase>
<testcase classname="Register registerSanitizer sanitizer tests should convert to imagekit url including formats" name="Register registerSanitizer sanitizer tests should convert to imagekit url including formats" time="0">
</testcase>
</testsuite>
</testsuites>
13 changes: 11 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
"clean": "rm -rf dist",
"lint": "prettier --check .",
"format": "prettier --write .",
"test:unit": "jest --coverage",
"test:unit:watch": "jest --watch",
"test:unit:ci": "CI=true jest --ci --runInBand --verbose --coverage",
"watch": "strapi-plugin watch",
"watch:link": "strapi-plugin watch:link",
"verify": "strapi-plugin verify",
Expand All @@ -52,14 +55,20 @@
"@strapi/strapi": "^5.13.0",
"@strapi/typescript-utils": "^5.13.0",
"@strapi/upload": "^5.13.0",
"@types/jest": "^29.5.14",
"@types/react": "^19.1.3",
"@types/react-dom": "^19.1.4",
"husky": "^9.1.7",
"jest": "^29.7.0",
"jest-cli": "^29.7.0",
"jest-junit": "^16.0.0",
"prettier": "^3.5.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.30.0",
"styled-components": "^6.1.18",
"ts-jest": "^29.3.4",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
},
"peerDependencies": {
Expand Down Expand Up @@ -98,7 +107,7 @@
"packageManager": "[email protected]",
"husky": {
"hooks": {
"pre-commit": "yarn format"
"pre-commit": "yarn format && yarn test:unit"
}
}
}
}
3 changes: 3 additions & 0 deletions server/src/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,6 @@ const bootstrap = async ({ strapi }: { strapi: Core.Strapi }) => {
};

export default bootstrap;

// Export functions for testing
export { addPermissions, registerUploadProvider, saveConfig };
200 changes: 200 additions & 0 deletions server/tests/bootstrap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { permissions, PLUGIN_ID } from '../../common';
import bootstrap from '../src/bootstrap';
import { imagekitMock } from './utils/plugins/imagekit';
import { uploadServiceMock } from './utils/plugins/imagekit/services';
import { getStrapiMock } from './utils/strapi';

describe('Bootstrap', () => {
beforeEach(() => {
jest.clearAllMocks();
});

describe('saveConfig', () => {
describe('when config does not exist in store', () => {
const strapiMock = getStrapiMock();

beforeEach(() => bootstrap({ strapi: strapiMock }));
it('should save default config if no config exists', async () => {
expect(strapiMock.store).toHaveBeenCalledTimes(1);
expect(strapiMock.store).toHaveBeenCalledWith({ type: 'plugin', name: PLUGIN_ID });

const get = strapiMock.store.mock.results.find((_) => _.type === 'return')?.value.get;

expect(get).toHaveBeenCalledTimes(1);
expect(get).toHaveBeenCalledWith({ key: 'config' });

expect(imagekitMock.config).toHaveBeenCalledTimes(14);

const set = strapiMock.store.mock.results.find((_) => _.type === 'return')?.value.set;
expect(set).toHaveBeenCalledTimes(1);
expect(set).toHaveBeenCalledWith({
key: 'config',
value: {
enabled: false,
publicKey: '',
privateKey: '',
urlEndpoint: '',
useSignedUrls: false,
expiry: 0,
uploadEnabled: false,
uploadOptions: {
tags: [],
folder: '',
overwriteTags: false,
overwriteCustomMetadata: false,
checks: '',
isPrivateFile: false,
},
useTransformUrls: false,
},
});

expect(imagekitMock.config.mock.calls).toEqual([
['enabled', false],
['publicKey', ''],
['privateKey', ''],
['urlEndpoint', ''],
['useSignedUrls', false],
['expiry', 0],
['uploadEnabled', false],
['useTransformUrls', false],
['uploadOptions.tags', []],
['uploadOptions.folder', ''],
['uploadOptions.overwriteTags', false],
['uploadOptions.overwriteCustomMetadata', false],
['uploadOptions.checks', ''],
['uploadOptions.isPrivateFile', false],
]);
});
});

describe('when config exists in store', () => {
const strapiMock = getStrapiMock({ storeConfig: {} });

beforeEach(() => bootstrap({ strapi: strapiMock }));
it('should not overwrite existing config', async () => {
expect(strapiMock.store).toHaveBeenCalledTimes(1);
expect(strapiMock.store).toHaveBeenCalledWith({ type: 'plugin', name: PLUGIN_ID });

const get = strapiMock.store.mock.results.find((_) => _.type === 'return')?.value.get;

expect(get).toHaveBeenCalledTimes(1);
expect(get).toHaveBeenCalledWith({ key: 'config' });

expect(imagekitMock.config).toHaveBeenCalledTimes(0);
const set = strapiMock.store.mock.results.find((_) => _.type === 'return')?.value.set;
expect(set).toHaveBeenCalledTimes(0);
});
});
});

describe('addPermissions', () => {
const strapiMock = getStrapiMock();

beforeEach(() => bootstrap({ strapi: strapiMock }));
it('should register all permissions', async () => {
expect(
strapiMock.admin.services.permission.actionProvider.registerMany
).toHaveBeenCalledTimes(1);
expect(strapiMock.admin.services.permission.actionProvider.registerMany).toHaveBeenCalledWith(
[
{
section: 'plugins',
displayName: 'Access ImageKit Media Library',
uid: permissions.mediaLibrary.read,
pluginName: PLUGIN_ID,
},
{
section: 'plugins',
displayName: 'Settings: Read',
uid: permissions.settings.read,
subCategory: 'settings',
pluginName: PLUGIN_ID,
},
{
section: 'plugins',
displayName: 'Settings: Change',
uid: permissions.settings.change,
subCategory: 'settings',
pluginName: PLUGIN_ID,
},
]
);
});
});

describe('registerUploadProvider', () => {
const strapiMock = getStrapiMock({
storeConfig: { uploadEnabled: true },
config: { uploadEnabled: true },
});

describe('upload is enabled', () => {
beforeEach(() => {
bootstrap({ strapi: strapiMock });
strapiMock
.plugin('imagekit')
.service('settings')
.getSettings.mockResolvedValue({ uploadEnabled: true });
});

it('upload is called from provider', async () => {
expect(uploadServiceMock.upload).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.upload({});
expect(uploadServiceMock.upload).toHaveBeenCalledTimes(1);
});

it('uploadStream is called from provider', async () => {
expect(uploadServiceMock.uploadStream).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.uploadStream({});
expect(uploadServiceMock.uploadStream).toHaveBeenCalledTimes(1);
});

it('delete is called from provider', async () => {
expect(uploadServiceMock.delete).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.delete({});
expect(uploadServiceMock.delete).toHaveBeenCalledTimes(1);
});

it('isPrivate is called from provider', async () => {
expect(uploadServiceMock.isPrivate).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.isPrivate({});
expect(uploadServiceMock.isPrivate).toHaveBeenCalledTimes(1);
});
});

describe('upload is disabled', () => {
beforeEach(() => {
bootstrap({ strapi: strapiMock });
strapiMock
.plugin('imagekit')
.service('settings')
.getSettings.mockResolvedValue({ uploadEnabled: false });
});

it('upload is called from provider', async () => {
expect(uploadServiceMock.upload).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.upload({});
expect(uploadServiceMock.upload).toHaveBeenCalledTimes(0);
});

it('uploadStream is called from provider', async () => {
expect(uploadServiceMock.uploadStream).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.uploadStream({});
expect(uploadServiceMock.uploadStream).toHaveBeenCalledTimes(0);
});

it('delete is called from provider', async () => {
expect(uploadServiceMock.delete).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.delete({});
expect(uploadServiceMock.delete).toHaveBeenCalledTimes(0);
});

it('isPrivate is called from provider', async () => {
expect(uploadServiceMock.isPrivate).toHaveBeenCalledTimes(0);
await strapiMock.plugin('upload').provider.isPrivate({});
expect(uploadServiceMock.isPrivate).toHaveBeenCalledTimes(0);
});
});
});
});
Loading