diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml index eccffaad..1f5ffcec 100644 --- a/.github/workflows/unittest.yml +++ b/.github/workflows/unittest.yml @@ -14,9 +14,10 @@ jobs: cache: 'yarn' - run: yarn - run: yarn test - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v5 with: - token: ${{ secrets.CODECOV_TOKEN }} files: ./plugin/coverage/clover.xml,./providers/deepl/coverage/clover.xml,./providers/libretranslate/coverage/clover.xml flags: unittests name: codecov-umbrella + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/plugin/__mocks__/initSetup.js b/plugin/__mocks__/initSetup.js index 0511d94b..a54f3d20 100644 --- a/plugin/__mocks__/initSetup.js +++ b/plugin/__mocks__/initSetup.js @@ -103,12 +103,12 @@ module.exports = ({ 'batch-translate-job': () => { const uid = 'plugin::translate.batch-translate-job' return { - findOne: this.db.query(uid).findOne, - find: this.db.query(uid).findMany, - count: this.db.query(uid).count, - create: this.db.query(uid).create, - update: this.db.query(uid).update, - delete: this.db.query(uid).delete, + findOne: mock.db.query(uid).findOne, + find: mock.db.query(uid).findMany, + count: mock.db.query(uid).count, + create: mock.db.query(uid).create, + update: mock.db.query(uid).update, + delete: mock.db.query(uid).delete, } }, }, diff --git a/plugin/server/services/__tests__/untranslated-service.test.js b/plugin/server/services/__tests__/untranslated-service.test.js new file mode 100644 index 00000000..15e2e30d --- /dev/null +++ b/plugin/server/services/__tests__/untranslated-service.test.js @@ -0,0 +1,119 @@ +'use strict' + +const service = require('../untranslated') + +describe('untranslated service', () => { + describe('getUntranslatedEntity', () => { + it('throws Error if Metadata is not found', () => { + const strapi = { + db: { + metadata: { + get: jest.fn(() => null), + }, + }, + } + + const untranslatedService = service({ strapi }) + + return expect(async () => + untranslatedService.getUntranslatedEntity({ uid: 'uid' }, {}) + ).rejects.toThrow('Content Type does not exist') + }) + + it('throws Error if content table is not localized', () => { + const strapi = { + db: { + metadata: { + get: jest.fn(() => ({ + attributes: { + localizations: {}, + }, + })), + }, + }, + } + + const untranslatedService = service({ strapi }) + + return expect(async () => + untranslatedService.getUntranslatedEntity({ uid: 'uid' }, {}) + ).rejects.toThrow('Content Type not localized') + }) + }) + + describe('getUntranslatedEntityIDs', () => { + it('throws Error if Metadata is not found', () => { + const strapi = { + db: { + metadata: { + get: jest.fn(() => null), + }, + }, + } + + const untranslatedService = service({ strapi }) + + return expect(async () => + untranslatedService.getUntranslatedEntityIDs({ uid: 'uid' }) + ).rejects.toThrow('Content Type does not exist') + }) + + it('throws Error if content table is not localized', () => { + const strapi = { + db: { + metadata: { + get: jest.fn(() => ({ + attributes: { + localizations: {}, + }, + })), + }, + }, + } + + const untranslatedService = service({ strapi }) + + return expect(async () => + untranslatedService.getUntranslatedEntityIDs({ uid: 'uid' }) + ).rejects.toThrow('Content Type not localized') + }) + }) + + describe('isFullyTranslated', () => { + it('throws Error if Metadata is not found', () => { + const strapi = { + db: { + metadata: { + get: jest.fn(() => null), + }, + }, + } + + const untranslatedService = service({ strapi }) + + return expect(async () => + untranslatedService.isFullyTranslated('uid', {}) + ).rejects.toThrow('Content Type does not exist') + }) + + it('throws Error if content table is not localized', () => { + const strapi = { + db: { + metadata: { + get: jest.fn(() => ({ + attributes: { + localizations: {}, + }, + })), + }, + }, + } + + const untranslatedService = service({ strapi }) + + return expect(async () => + untranslatedService.isFullyTranslated('uid', {}) + ).rejects.toThrow('Content Type not localized') + }) + }) +}) diff --git a/plugin/server/services/batch-translate/__tests__/BatchTranslateJob.test.js b/plugin/server/services/batch-translate/__tests__/BatchTranslateJob.test.js new file mode 100644 index 00000000..5f452076 --- /dev/null +++ b/plugin/server/services/batch-translate/__tests__/BatchTranslateJob.test.js @@ -0,0 +1,154 @@ +'use strict' + +const { BatchTranslateJob } = require('../BatchTranslateJob') + +describe('BatchTranslateJob', () => { + beforeEach(() => { + jest.clearAllMocks() + Object.defineProperty(global, 'strapi', { + value: require('../../../../__mocks__/initSetup')({ + contentTypes: { + nonTranslatedContentType: { + pluginOptions: { i18n: { localized: false } }, + }, + translatedContentType: { + pluginOptions: { i18n: { localized: true } }, + }, + }, + }), + writable: true, + }) + }) + + it("constructor throws if content type isn't localized", () => { + expect(() => { + new BatchTranslateJob({ + id: 'id', + contentType: 'nonTranslatedContentType', + sourceLocale: 'sourceLocale', + targetLocale: 'targetLocale', + entityIds: ['entityIds'], + status: 'status', + autoPublish: false, + }) + }).toThrow('translate.batch-translate.content-type-not-localized') + }) + + it('constructor does not throw if content type is localized', () => { + expect(() => { + new BatchTranslateJob({ + id: 'id', + contentType: 'translatedContentType', + sourceLocale: 'sourceLocale', + targetLocale: 'targetLocale', + entityIds: ['entityIds'], + status: 'status', + autoPublish: false, + }) + }).not.toThrow() + }) + + describe('start', () => { + it('throws for status !== "created"', async () => { + const job = new BatchTranslateJob({ + id: 'id', + contentType: 'translatedContentType', + sourceLocale: 'sourceLocale', + targetLocale: 'targetLocale', + entityIds: ['entityIds'], + status: 'status', + autoPublish: false, + }) + + expect(async () => job.start()).rejects.toThrow() + }) + + it('does not throw for status === "created"', async () => { + const job = new BatchTranslateJob({ + id: 'id', + contentType: 'translatedContentType', + sourceLocale: 'sourceLocale', + targetLocale: 'targetLocale', + entityIds: ['entityIds'], + status: 'created', + autoPublish: false, + }) + + expect(job.start()).resolves.not.toThrow() + }) + }) + + describe('status updates', () => { + let job + + beforeEach(() => { + job = new BatchTranslateJob({ + id: 'id', + contentType: 'translatedContentType', + sourceLocale: 'sourceLocale', + targetLocale: 'targetLocale', + entityIds: ['entityIds'], + status: 'created', + autoPublish: false, + }) + }) + + it("cancel updates the status to 'cancelled' if it is running", async () => { + const f = jest.fn() + BatchTranslateJob.prototype.updateStatus = f + + await job.cancel() + + expect(f).toHaveBeenCalledWith('cancelled') + }) + + it('cancel does nothing if job is not running', async () => { + const f = jest.fn() + BatchTranslateJob.prototype.updateStatus = f + + job.status = 'finished' + await job.cancel() + + expect(f).not.toHaveBeenCalled() + }) + + it("pause updates the status to 'paused' if it is running", async () => { + const f = jest.fn() + BatchTranslateJob.prototype.updateStatus = f + + await job.pause() + + expect(f).toHaveBeenCalledWith('paused') + }) + + it('pause does nothing if job is not running', async () => { + const f = jest.fn() + BatchTranslateJob.prototype.updateStatus = f + + job.status = 'finished' + + await job.pause() + + expect(f).not.toHaveBeenCalled() + }) + + it('setup changes the status to setup and then running', async () => { + const f = jest.fn((status) => (job.status = status)) + BatchTranslateJob.prototype.updateStatus = f + + await job.setup() + + expect(f).toHaveBeenCalledWith('setup') + expect(f).toHaveBeenCalledWith('running') + }) + + it('start changes the status to finished after successful termination', async () => { + const f = jest.fn((status) => (job.status = status)) + BatchTranslateJob.prototype.updateStatus = f + + await job.start() + + expect(f).toHaveBeenCalledWith('finished') + }) + }) +}) diff --git a/plugin/server/utils/__tests__/dummy-provider.test.js b/plugin/server/utils/__tests__/dummy-provider.test.js new file mode 100644 index 00000000..5b743696 --- /dev/null +++ b/plugin/server/utils/__tests__/dummy-provider.test.js @@ -0,0 +1,72 @@ +'use strict' + +const { provider, name, init } = require('../dummy-provider') + +describe('Dummy Provider', () => { + it('meta data is correct', () => { + expect(provider).toBe('dummy') + expect(name).toBe('Dummy') + }) + + describe('translation function', () => { + let translate + + beforeAll(() => { + translate = init().translate + }) + + it('throws if targetLocale is not defined', () => { + return expect(async () => + translate({ sourceLocale: 'de', text: 'Lorem Ipsum' }) + ).rejects.toBeTruthy() + }) + + it('throws if sourceLocale is not defined', () => { + return expect(async () => + translate({ targetLocale: 'de', text: 'Lorem Ipsum' }) + ).rejects.toBeTruthy() + }) + + it('returns empty Array if no text is give', async () => { + expect( + await translate({ sourceLocale: 'de', targetLocale: 'en' }) + ).toEqual([]) + }) + + it('return array of input if string is given', async () => { + expect( + await translate({ + sourceLocale: 'de', + targetLocale: 'anything', + text: 'Give me this as an array', + }) + ).toEqual(['Give me this as an array']) + }) + + it('return input if array of strings is given', async () => { + expect( + await translate({ + sourceLocale: 'de', + targetLocale: 'anything', + text: ['Give', 'me', 'this back'], + }) + ).toEqual(['Give', 'me', 'this back']) + }) + }) + + it('usage function is static', async () => { + let { usage, translate } = init() + + const usageResult1 = await usage() + + await translate({ + sourceLocale: 'de', + targetLocale: 'en', + text: 'Some random long text that should increase the usage, but does not, because this is the dummy provider.', + }) + + const usageResult2 = await usage() + + expect(usageResult1).toEqual(usageResult2) + }) +}) diff --git a/providers/libretranslate/lib/__tests__/client.test.js b/providers/libretranslate/lib/__tests__/client.test.js index e2a1b9e4..29a28bef 100644 --- a/providers/libretranslate/lib/__tests__/client.test.js +++ b/providers/libretranslate/lib/__tests__/client.test.js @@ -69,13 +69,22 @@ describe('libretranslate client', () => { beforeEach(() => { server.use(http.post(`${VALID_URL}/translate`, translateHandler)) server.use(http.get(`${VALID_URL}/languages`, languagesHandler)) - server.use(http.get(`${INVALID_URL}/languages`, () => new HttpResponse())) }) - it('sets LocaleInformation default values', () => { - const client = new Client(INVALID_URL) + it('sets LocaleInformation default values', async () => { + server.use( + http.get(`${INVALID_URL}/languages`, async () => { + await new Promise((r) => setTimeout(r, 200)) + return new HttpResponse(null, { status: 500 }) + }) + ) + const client = new Client(INVALID_URL) expect(client.localeInformation).toBeDefined() + expect(client.localeInformation.length).toBe(30) + + // wait until sever response is resolved, prevents logging after test completion + await new Promise((r) => setTimeout(r, 200)) }) it('sets LocaleInformation according to server response', async () => { diff --git a/providers/libretranslate/lib/__tests__/libretranslate.test.js b/providers/libretranslate/lib/__tests__/libretranslate.test.js index 6f3f0685..65ac6445 100644 --- a/providers/libretranslate/lib/__tests__/libretranslate.test.js +++ b/providers/libretranslate/lib/__tests__/libretranslate.test.js @@ -82,6 +82,9 @@ describe('libretranslate provider', () => { } beforeAll(() => { server = getServer() + server.use( + http.get(`${BASE_URL}/languages`, () => HttpResponse.json(enabledLocales)) + ) Object.defineProperty(global, 'strapi', { value: require('../../__mocks__/initStrapi')({}), @@ -115,10 +118,12 @@ describe('libretranslate provider', () => { http.post( `${BASE_URL}/translate`, buildTranslateHandler({ maxTexts: 50, maxChars: 10000 }) - ), - http.get(`${BASE_URL}/languages`, async (req, res, ctx) => { - return res(ctx.json(enabledLocales)) - }) + ) + ) + server.use( + http.get(`${BASE_URL}/languages`, async () => + HttpResponse.json(enabledLocales) + ) ) ltProvider = provider.init({ apiUrl: BASE_URL,