diff --git a/README.md b/README.md index d7baf00..8e40103 100644 --- a/README.md +++ b/README.md @@ -3,28 +3,35 @@ A Cloudflare Worker that utilises D1 (SQLite) to create a short url for a provid This project is an excuse to try out some new tools (namely Cloudflare Workers and D1). However, it also serves as an example of how you can build, deploy and test via CI. # Building -`.github/workflows` describes the process in a repeatable way. + +`.github/workflows` describes the process in a repeatable way. # Local Development + `package.json` contains some commands for local development. `npm i && npm start` _should_ just work. # The Design + ## Architecture + TODO add diagram ## Shortfalls + ### Hashing algorithm + Some decisions have been made in order to keep the project fun, but which would limit real life usage: + - MD5 hashing used (known to eventually conflict) - The MD5 hash is also split to reduce the size, further increasing the risk of conflict I may improve this at some point as it's own challenge, but the problem stands today. ## Positives + On the flip side, there's some positives: + - Using Cloudflare Workers is serverless, so less to worry about in terms of scaling - Using Cloudflare D1, also serverless (SQLite) with automatic read replicas The Wrangler tooling also works very nicely for local development. - - diff --git a/src/index.ts b/src/index.ts index 0e3b832..2040cd6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -16,15 +16,15 @@ export default { if (!urlParam) { return new Response('Invalid url', { status: 400 }); } - let longUrl = "" - + let longUrl = ''; + // convert to a string - TODO introduce Joi or Zod for validation try { longUrl = new URL(urlParam).toString(); } catch (e) { return new Response('Invalid url', { status: 400 }); } - + // hash it const hashBuffer = await crypto.subtle.digest('MD5', new TextEncoder().encode(longUrl)); const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array diff --git a/test/integration/create-url.test.ts b/test/integration/create-url.test.ts index b20cb2c..4793a08 100644 --- a/test/integration/create-url.test.ts +++ b/test/integration/create-url.test.ts @@ -41,7 +41,7 @@ describe('Worker', () => { expect(resp.status).toBe(400); }); - + it('should return 400 for invalid url', async () => { const resp = await worker.fetch('/api/shorten?url=woop', { method: 'POST', @@ -50,8 +50,29 @@ describe('Worker', () => { expect(resp.status).toBe(400); }); - // should return the same url if it is already shortened (x2 times) - // should return a shortened url for a very long url - // should return a shortened url for a url with special characters + it('should return the same url if it is already shortened (x2 times)', async () => { + const shortenUrl = async () => + await worker.fetch('/api/shorten?url=https://example.com', { + method: 'POST', + }); + + const [first, second] = await Promise.all([shortenUrl(), shortenUrl()]); + + expect(first.json()).toEqual(second.json()); + }); + + it('should return a shortened url for a very long url', async () => { + const resp = await worker.fetch('/api/shorten?url=https://example-example-example-example-example-example-example-example-example-example-example-example-example-example.com?somequerystring=onetwothree&another=fourfivesixseveneight', { + method: 'POST', + }); + const text = await resp.json(); + + expect(text).toEqual({ + shortUrl: 'NjEyZT', + }); + expect(resp.status).toBe(200); + }); + // should return 400 if attempting to shorten an already shortened url + // should only store a url once if already shortened });