diff --git a/__tests__/unit/shortener.spec.ts b/__tests__/unit/shortener.spec.ts new file mode 100644 index 0000000..0cc1b77 --- /dev/null +++ b/__tests__/unit/shortener.spec.ts @@ -0,0 +1,53 @@ +import Shortener from '../../lib/shortener'; + +jest.mock('../../db', () => { + return { + __esModule: true, + default: { + get: jest + .fn() + .mockResolvedValueOnce('https://rsgarxia.is-a.dev') + .mockResolvedValueOnce({ + status: 'error', + reason: 'URL not found', + }), + set: jest.fn().mockResolvedValueOnce(true), + }, + }; +}); + +describe('Shortener', () => { + describe('urlByHash method', () => { + describe('if Hash exists', () => { + it('returns URL', async () => { + const url = await Shortener.urlByHash('some-uuid'); + + expect(url).toBe('https://rsgarxia.is-a.dev'); + }); + }); + + describe('if Hash does not exist', () => { + it('returns JSON with error message', async () => { + const result = await Shortener.urlByHash('some-uuid'); + + expect(result).toMatchObject({ + reason: expect.any(String), + status: expect.any(String), + }); + }); + }); + }); + + describe('hashUrl method', () => { + it('returns JSON with URL hash', async () => { + const url = 'https://rsgarxia.is-a.dev'; + const hash = await Shortener.hashUrl(url); + + expect(hash).toMatchObject({ + status: 'success', + original_url: url, + shortened_url: expect.any(String), + }); + }); + }); +}); diff --git a/jest.config.js b/jest.config.js index b413e10..b9db607 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,7 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ module.exports = { + verbose: true, preset: 'ts-jest', testEnvironment: 'node', -}; \ No newline at end of file + testPathIgnorePatterns: ['dist/*', 'node_modules'], +}; diff --git a/lib/shortener.ts b/lib/shortener.ts new file mode 100644 index 0000000..75598bf --- /dev/null +++ b/lib/shortener.ts @@ -0,0 +1,29 @@ +import client from '../db'; +import mmh3 from 'murmurhash3'; + +const DOMAIN = process.env.DOMAIN || 'https://frodo.sigfried.xyz'; + +class Shortener { + static async urlByHash(hash: string) { + const url = await client.get(hash); + + return url ? url : { status: 'error', reason: 'URL not found' }; + } + + static async hashUrl(url: string) { + const hash = mmh3.murmur32HexSync(url); + + const maybeKeyValue = await this.urlByHash(hash); + if (!maybeKeyValue) { + await client.set(hash, url); + } + + return { + status: 'success', + original_url: url, + shortened_url: `${DOMAIN}/${hash}`, + }; + } +} + +export default Shortener;