From 2f08030b8f4d06d1e7d7454403e9b7c458f430f5 Mon Sep 17 00:00:00 2001 From: Cesar Augusto Date: Mon, 28 Oct 2024 22:21:07 -0300 Subject: [PATCH] Add more upload unit tests --- src/__tests__/fixtures.ts | 56 ++++++++++-- src/__tests__/parser.test.ts | 22 +++-- src/__tests__/test_upload.png | Bin 0 -> 21792 bytes src/__tests__/test_upload2.png | Bin 0 -> 16630 bytes src/__tests__/test_upload_error.png | Bin 0 -> 11381 bytes src/__tests__/upload.test.ts | 127 ++++++++++++++++++---------- src/uploaders/nip96.ts | 2 +- 7 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 src/__tests__/test_upload.png create mode 100644 src/__tests__/test_upload2.png create mode 100644 src/__tests__/test_upload_error.png diff --git a/src/__tests__/fixtures.ts b/src/__tests__/fixtures.ts index 0b7f260..d3470f9 100644 --- a/src/__tests__/fixtures.ts +++ b/src/__tests__/fixtures.ts @@ -5,7 +5,9 @@ import { readFile } from 'fs/promises' import { join } from 'node:path' import type { NostrEvent } from 'nostr-tools' import { Markdown as MarkdownExtension } from 'tiptap-markdown' +import type { Mock } from 'vitest' import { test as base } from 'vitest' +import type { FileUploadExtension } from '../extensions/FileUploadExtension' import { NostrExtension } from '../extensions/NostrExtension' const extensions = [ @@ -23,20 +25,28 @@ const extensions = [ }), ] -const editor = new Editor({ extensions }) - type Fixtures = { - editor: typeof editor + editor: Editor editorMarkdown: Editor editorUserAbout: Editor getFile: (filaneme: string) => Promise + fileUploadExtension: (editor: Editor) => typeof FileUploadExtension + fileUploadSpies: (editor: Editor) => { + spySign: Mock + spyHash: Mock + spyDrop: Mock + spyStart: Mock + spyUpload: Mock + spyUploadError: Mock + spyComplete: Mock + } } // We ideally want to have a single editor instance to parse markdown and user abouts, // But currently no ideal way to dynamically load extensions export const test = base.extend({ editor: ({}, use) => { - return use(editor) + return use(new Editor({ extensions })) }, editorMarkdown: ({}, use) => { return use( @@ -62,7 +72,43 @@ export const test = base.extend({ getFile: ({}, use) => { return use(async (filename: string) => { const buffer = await readFile(join(__dirname, filename)) - return new File([buffer], 'image.jpg', { type: 'image/jpeg' }) + return new File([buffer], filename, { type: 'image/png' }) + }) + }, + fileUploadExtension: ({}, use) => { + return use((editor: Editor) => { + return editor.extensionManager.extensions.find((x) => x.name === 'fileUpload') as typeof FileUploadExtension + }) + }, + fileUploadSpies: ({ fileUploadExtension }, use) => { + return use((editor: Editor) => { + const fileUpload = fileUploadExtension(editor) + + const spySign = vitest.fn() + const spyHash = vitest.fn() + const spyDrop = vitest.fn() + const spyStart = vitest.fn() + const spyUpload = vitest.fn() + const spyUploadError = vitest.fn() + const spyComplete = vitest.fn() + + fileUpload.options.sign = spySign + fileUpload.options.hash = spyHash + fileUpload.options.onDrop = spyDrop + fileUpload.options.onStart = spyStart + fileUpload.options.onUpload = spyUpload + fileUpload.options.onUploadError = spyUploadError + fileUpload.options.onComplete = spyComplete + + return { + spySign, + spyHash, + spyDrop, + spyStart, + spyUpload, + spyUploadError, + spyComplete, + } }) }, }) diff --git a/src/__tests__/parser.test.ts b/src/__tests__/parser.test.ts index ea97968..d3bdef0 100644 --- a/src/__tests__/parser.test.ts +++ b/src/__tests__/parser.test.ts @@ -67,9 +67,10 @@ describe('parseNote()', () => { "hash": null, "sha256": null, "src": "http://host.com/image", + "tags": null, "uploadError": null, - "uploadType": "nip96", - "uploadUrl": "https://nostr.build", + "uploadType": "blossom", + "uploadUrl": "https://localhost:3000", "uploading": false, }, "type": "image", @@ -89,6 +90,7 @@ describe('parseNote()', () => { "file": null, "sha256": null, "src": "http://host.com/video", + "tags": null, "uploadError": null, "uploadType": "nip96", "uploadUrl": "https://nostr.build", @@ -230,9 +232,10 @@ describe('parseNote()', () => { "hash": null, "sha256": null, "src": "https://nostr.com/img.jpg", + "tags": null, "uploadError": null, - "uploadType": "nip96", - "uploadUrl": "https://nostr.build", + "uploadType": "blossom", + "uploadUrl": "https://localhost:3000", "uploading": false, }, "type": "image", @@ -252,6 +255,7 @@ describe('parseNote()', () => { "file": null, "sha256": null, "src": "https://v.nostr.build/g6BQ.mp4", + "tags": null, "uploadError": null, "uploadType": "nip96", "uploadUrl": "https://nostr.build", @@ -482,9 +486,10 @@ https://host.com/2.jpeg "hash": null, "sha256": null, "src": "https://host.com/1.jpeg", + "tags": null, "uploadError": null, - "uploadType": "nip96", - "uploadUrl": "https://nostr.build", + "uploadType": "blossom", + "uploadUrl": "https://localhost:3000", "uploading": false, }, "type": "image", @@ -504,9 +509,10 @@ https://host.com/2.jpeg "hash": null, "sha256": null, "src": "https://host.com/2.jpeg", + "tags": null, "uploadError": null, - "uploadType": "nip96", - "uploadUrl": "https://nostr.build", + "uploadType": "blossom", + "uploadUrl": "https://localhost:3000", "uploading": false, }, "type": "image", diff --git a/src/__tests__/test_upload.png b/src/__tests__/test_upload.png new file mode 100644 index 0000000000000000000000000000000000000000..bd08725c6d69646968881673e8b357be572d5e30 GIT binary patch literal 21792 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 X``V-NIQd7HC*zs&3ndiT;@$rNQ3O0g literal 0 HcmV?d00001 diff --git a/src/__tests__/test_upload_error.png b/src/__tests__/test_upload_error.png new file mode 100644 index 0000000000000000000000000000000000000000..40479eb1c846ec65a5ec70b55ea6b3d9d8b6403e GIT binary patch literal 11381 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 { const contentType = info.request.headers.get('content-type') const buffer = (await info.request.body?.getReader().read())?.value if (buffer) { const sha256 = bufferToHex(await crypto.subtle.digest('SHA-256', buffer.buffer)) + // Test error file + if (sha256 === hash3) { + return HttpResponse.json({ message: 'Invalid file' }, { status: 401 }) + } return HttpResponse.json({ sha256, url: `https://localhost:3000/${sha256}`, @@ -34,34 +55,17 @@ describe('FileUpload', () => { server.resetHandlers() }) - test('assert 2 successfully file uploads', async ({ editor, getFile }) => { - const fileUpload = editor.extensionManager.extensions.find( - (x) => x.name === 'fileUpload', - ) as typeof FileUploadExtension - - const spySign = vitest.fn() - const spyHash = vitest.fn() - const spyStart = vitest.fn() - const spyUpload = vitest.fn() - const spyComplete = vitest.fn() + test('assert 2 successfully file uploads', async ({ editor, getFile, fileUploadSpies }) => { + const { spySign, spyHash, spyDrop, spyStart, spyUpload, spyUploadError, spyComplete } = fileUploadSpies(editor) - fileUpload.options.sign = spySign - fileUpload.options.hash = spyHash - fileUpload.options.onStart = spyStart - fileUpload.options.onUpload = spyUpload - fileUpload.options.onComplete = spyComplete - - editor.setOptions() const file = await getFile('test_upload.png') const file2 = await getFile('test_upload2.png') editor.commands.setContent('GM!') - editor.commands.addFile(file, editor.$doc.size - 2) editor.commands.addFile(file2, editor.$doc.size - 2) + editor.commands.addFile(file, editor.$doc.size - 2) - // less than ideal await new Promise((resolve) => setTimeout(() => resolve())) - const schema = editor.getJSON() expect(schema.content).toHaveLength(3) @@ -73,42 +77,79 @@ describe('FileUpload', () => { expect(schema.content?.[2].attrs?.sha256).toBe(null) expect(schema.content?.[2].attrs?.src).toContain('blob:nodedata') - editor.commands.uploadFiles() + const files = await editor.storage.fileUpload.uploader.start() - await new Promise((resolve) => setTimeout(() => resolve(), 100)) + expect(files).toHaveLength(2) const schema2 = editor.getJSON() - const hash1 = '008a2224c4d2a513ab2a4add09a2ac20c2d9cec1144b5111bc1317edb2366eac' - const hash2 = '6c36995913e97b73d5365f93a7b524a9e45edc68e4f11b78060154987c53602c' expect(schema2.content?.[1].attrs?.sha256).toStrictEqual(hash1) expect(schema2.content?.[1].attrs?.src).toStrictEqual(`https://localhost:3000/${hash1}`) expect(schema2.content?.[2].attrs?.sha256).toStrictEqual(hash2) expect(schema2.content?.[2].attrs?.src).toStrictEqual(`https://localhost:3000/${hash2}`) - const files = [ - { - sha256: '008a2224c4d2a513ab2a4add09a2ac20c2d9cec1144b5111bc1317edb2366eac', - url: 'https://localhost:3000/008a2224c4d2a513ab2a4add09a2ac20c2d9cec1144b5111bc1317edb2366eac', - type: 'image/jpeg', - size: 16630, - }, - { - sha256: '6c36995913e97b73d5365f93a7b524a9e45edc68e4f11b78060154987c53602c', - url: 'https://localhost:3000/6c36995913e97b73d5365f93a7b524a9e45edc68e4f11b78060154987c53602c', - type: 'image/jpeg', - size: 21792, - }, - ] - expect(editor.storage.fileUpload).toStrictEqual({ files }) expect(spySign).toHaveBeenCalledTimes(2) expect(spyHash).toHaveBeenCalledTimes(2) + expect(spyDrop).toHaveBeenCalledTimes(2) expect(spyStart).toHaveBeenCalledOnce() - expect(spyUpload).toHaveBeenNthCalledWith(1, editor, files[0]) - expect(spyUpload).toHaveBeenNthCalledWith(2, editor, files[1]) + expect(spyUpload).toHaveBeenNthCalledWith(1, editor, res1) + expect(spyUpload).toHaveBeenNthCalledWith(2, editor, res2) + expect(spyUploadError).not.toHaveBeenCalled() expect(spyComplete).toHaveBeenNthCalledWith(1, editor, files) expect(editor.getText({ blockSeparator: ' ' })).toStrictEqual( - `GM! https://localhost:3000/008a2224c4d2a513ab2a4add09a2ac20c2d9cec1144b5111bc1317edb2366eac https://localhost:3000/6c36995913e97b73d5365f93a7b524a9e45edc68e4f11b78060154987c53602c`, + `GM! https://localhost:3000/${hash1} https://localhost:3000/${hash2}`, ) }) + + test('assert error upload', async ({ editor, getFile, fileUploadSpies }) => { + const { spyDrop, spyUpload, spyUploadError, spyComplete } = fileUploadSpies(editor) + + const file = await getFile('test_upload.png') + const file2 = await getFile('test_upload_error.png') + + editor.commands.setContent('GM!') + editor.commands.addFile(file2, editor.$doc.size - 2) + editor.commands.addFile(file, editor.$doc.size - 2) + + await new Promise((resolve) => setTimeout(() => resolve())) + await expect(editor.storage.fileUpload.uploader.start()).rejects.toStrictEqual(new Error('Error: Invalid file')) + + const schema2 = editor.getJSON() + expect(schema2.content?.[2].attrs?.sha256).toBeNull() + expect(schema2.content?.[2].attrs?.uploadError).toStrictEqual('Error: Invalid file') + expect(schema2.content?.[1].attrs?.sha256).toStrictEqual(hash1) + expect(schema2.content?.[1].attrs?.uploadError).toBeNull() + + expect(spyDrop).toHaveBeenCalledTimes(2) + expect(spyUpload).toHaveBeenCalledOnce() + expect(spyUpload).toHaveBeenCalledWith(editor, res1) + expect(spyUploadError).toHaveBeenCalledOnce() + expect(spyUploadError).toHaveBeenCalledWith(editor, { uploadError: 'Error: Invalid file' }) + expect(spyComplete).not.toHaveBeenCalledOnce() + }) + + test('assert uploads with immediateUpload true', async ({ + editor, + getFile, + fileUploadExtension, + fileUploadSpies, + }) => { + const fileUpload = fileUploadExtension(editor) + fileUpload.options.immediateUpload = true + + const { spyDrop, spyStart, spyUpload, spyUploadError, spyComplete } = fileUploadSpies(editor) + + const file = await getFile('test_upload.png') + + editor.commands.setContent('GM!') + editor.commands.addFile(file, editor.$doc.size - 2) + + await new Promise((resolve) => setTimeout(() => resolve(), 100)) + + expect(spyDrop).toHaveBeenCalledOnce() + expect(spyStart).toHaveBeenCalledOnce() + expect(spyUpload).toHaveBeenCalledOnce() + expect(spyUploadError).not.toHaveBeenCalled() + expect(spyComplete).toHaveBeenCalledOnce() + }) }) diff --git a/src/uploaders/nip96.ts b/src/uploaders/nip96.ts index def465e..43f1546 100644 --- a/src/uploaders/nip96.ts +++ b/src/uploaders/nip96.ts @@ -12,7 +12,7 @@ export interface NIP96Options { export async function uploadNIP96(options: NIP96Options) { if (!options.sign) { - return Promise.reject('No signer found') + throw new Error('No signer provided') } try { const server = await readServerConfig(options.serverUrl)