From dee3a08c8fdd63503c6e783435b70627e83087a4 Mon Sep 17 00:00:00 2001 From: Jersey Date: Sun, 7 Jul 2024 16:12:43 -0400 Subject: [PATCH] add post editing --- src/api/socket.ts | 4 +- src/interfaces/post.ts | 37 ++++ tests/internal/deps.ts | 6 + tests/internal/post.ts | 62 +++---- tests/post.ts | 381 +++++++++++++++++++++++------------------ 5 files changed, 292 insertions(+), 198 deletions(-) create mode 100644 tests/internal/deps.ts diff --git a/src/api/socket.ts b/src/api/socket.ts index 7ca784a..1635d34 100644 --- a/src/api/socket.ts +++ b/src/api/socket.ts @@ -97,13 +97,13 @@ export class socket extends EventEmitter<{ ) return; if (packet.cmd) return; - const post = new post({ + const p = new post({ api_token: this.opts.api_token, api_url: this.opts.api_url, data: packet.val as unknown as api_post, }); - this.emit('post', post); + this.emit('post', p); }); } diff --git a/src/interfaces/post.ts b/src/interfaces/post.ts index 5cec506..5a42edf 100644 --- a/src/interfaces/post.ts +++ b/src/interfaces/post.ts @@ -53,6 +53,16 @@ export interface post_report_options { comment: string; } +/** post update options */ +export interface post_update_options { + /** new content */ + content?: string; + /** new attachments */ + attachments?: string[]; + /** nonce */ + nonce?: string; +} + /** check if a value is a post */ export function is_api_post(obj: unknown): obj is api_post { if (obj === null || typeof obj !== 'object') return false; @@ -211,4 +221,31 @@ export class post { }); } } + + /** edit the post */ + async update(opts: post_update_options) { + const resp = await fetch(`${this.api_url}/posts/?id=${this.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + 'token': this.api_token, + }, + body: JSON.stringify({ + attachments: opts.attachments ?? this.attachments, + content: opts.content ?? this.content, + nonce: opts.nonce ?? undefined, + }), + }); + + const data = await resp.json(); + + if (!resp.ok || data.error) { + throw new Error('failed to update post', { + cause: data, + }); + } + + this.raw = data; + this.assign_data(); + } } diff --git a/tests/internal/deps.ts b/tests/internal/deps.ts new file mode 100644 index 0000000..76143a4 --- /dev/null +++ b/tests/internal/deps.ts @@ -0,0 +1,6 @@ +export { + assertEquals, + assertRejects, + assertThrows, +} from 'jsr:@std/assert@0.226.0'; +export { mockFetch } from 'jsr:@c4spar/mock-fetch@1.0.0'; diff --git a/tests/internal/post.ts b/tests/internal/post.ts index 9b657c6..3371fbf 100644 --- a/tests/internal/post.ts +++ b/tests/internal/post.ts @@ -1,39 +1,43 @@ import { assertEquals } from 'jsr:@std/assert@0.226.0'; -import { type post, type api_post, post_type } from '../../src/interfaces/post.ts'; +import { + type api_post, + type post, + post_type, +} from '../../src/interfaces/post.ts'; export function post_is_api_post(post: post, api_post: api_post) { - assertEquals(post.id, api_post._id); - assertEquals(post.deleted, api_post.isDeleted); - assertEquals(post.content, api_post.p); - assertEquals(post.pinned, api_post.pinned); - assertEquals(post.id, api_post.post_id); - assertEquals(post.chat_id, api_post.post_origin); - assertEquals(post.timestamp, api_post.t.e); - assertEquals(post.type, api_post.type); - assertEquals(post.username, api_post.u); - if (api_post.bridged && post.bridged) { - post_is_api_post(post.bridged, api_post.bridged); - } else { - assertEquals(post.bridged, undefined); - } + assertEquals(post.id, api_post._id); + assertEquals(post.deleted, api_post.isDeleted); + assertEquals(post.content, api_post.p); + assertEquals(post.pinned, api_post.pinned); + assertEquals(post.id, api_post.post_id); + assertEquals(post.chat_id, api_post.post_origin); + assertEquals(post.timestamp, api_post.t.e); + assertEquals(post.type, api_post.type); + assertEquals(post.username, api_post.u); + if (api_post.bridged && post.bridged) { + post_is_api_post(post.bridged, api_post.bridged); + } else { + assertEquals(post.bridged, undefined); + } } export const regular_post: api_post = { - _id: 'test', - attachments: [], - isDeleted: false, - p: 'test', - pinned: false, - post_id: 'test', - post_origin: 'test', - t: { - e: 0, - }, - type: post_type.normal, - u: 'test', + _id: 'test', + attachments: [], + isDeleted: false, + p: 'test', + pinned: false, + post_id: 'test', + post_origin: 'test', + t: { + e: 0, + }, + type: post_type.normal, + u: 'test', }; export const bridged_post: api_post = { - ...regular_post, - bridged: regular_post, + ...regular_post, + bridged: regular_post, }; diff --git a/tests/post.ts b/tests/post.ts index aed7ac9..6ecbc33 100644 --- a/tests/post.ts +++ b/tests/post.ts @@ -1,185 +1,232 @@ import { is_api_post, post } from '../src/interfaces/post.ts'; -import { assertEquals, assertThrows, assertRejects } from 'jsr:@std/assert@0.226.0'; -import { mockFetch } from 'jsr:@c4spar/mock-fetch@1.0.0'; -import { post_is_api_post, bridged_post, regular_post } from './internal/post.ts'; +import { + assertEquals, + assertRejects, + assertThrows, + mockFetch, +} from './internal/deps.ts'; +import { + bridged_post, + post_is_api_post, + regular_post, +} from './internal/post.ts'; Deno.test('api_post validation', async (i) => { - await i.step('number (invalid)', () => { - assertEquals(is_api_post(1), false); - }); + await i.step('number (invalid)', () => { + assertEquals(is_api_post(1), false); + }); - await i.step('string (invalid)', () => { - assertEquals(is_api_post('test'), false); - }); + await i.step('string (invalid)', () => { + assertEquals(is_api_post('test'), false); + }); - await i.step('empty object (invalid)', () => { - assertEquals(is_api_post({}), false); - }); + await i.step('empty object (invalid)', () => { + assertEquals(is_api_post({}), false); + }); - await i.step('post (valid, not bridged)', () => { - assertEquals(is_api_post(regular_post), true); - }); + await i.step('post (valid, not bridged)', () => { + assertEquals(is_api_post(regular_post), true); + }); - await i.step('post (valid, bridged)', () => { - assertEquals(is_api_post(bridged_post), true); - }); + await i.step('post (valid, bridged)', () => { + assertEquals(is_api_post(bridged_post), true); + }); }); Deno.test('post construction', async (i) => { - await i.step('throw error if data is not a post', () => { - assertThrows(() => { - new post({ - api_url: 'http://localhost:8000', - api_token: 'test', - // @ts-ignore: intentionally passing an empty object - data: {}, - }); - }); - }); - - await i.step('construct valid post (not bridged)', () => { - const p = new post({ - api_url: 'http://localhost:8000', - api_token: 'test', - data: regular_post, - }); - - post_is_api_post(p, regular_post); - }); - - await i.step('construct valid post (bridged)', () => { - const p = new post({ - api_url: 'http://localhost:8000', - api_token: 'test', - data: bridged_post, - }); - - post_is_api_post(p, bridged_post); - }); + await i.step('throw error if data is not a post', () => { + assertThrows(() => { + new post({ + api_url: 'http://localhost:8000', + api_token: 'test', + // @ts-ignore: intentionally passing an empty object + data: {}, + }); + }); + }); + + await i.step('construct valid post (not bridged)', () => { + const p = new post({ + api_url: 'http://localhost:8000', + api_token: 'test', + data: regular_post, + }); + + post_is_api_post(p, regular_post); + }); + + await i.step('construct valid post (bridged)', () => { + const p = new post({ + api_url: 'http://localhost:8000', + api_token: 'test', + data: bridged_post, + }); + + post_is_api_post(p, bridged_post); + }); }); Deno.test('post pinning', async (i) => { - const p = new post({ - api_url: 'http://localhost:8000', - api_token: 'test', - data: regular_post, - }); - - await i.step('pin (successful)', async () => { - mockFetch('http://localhost:8000/posts/:id/pin', { - body: JSON.stringify({ - ...regular_post, - pinned: true, - }) - }) - - await p.pin(); - - assertEquals(p.pinned, true); - }); - - await i.step('unpin (successful)', async () => { - mockFetch('http://localhost:8000/posts/:id/pin', { - body: JSON.stringify({ - ...regular_post, - pinned: false, - }) - }) - - await p.unpin(); - - assertEquals(p.pinned, false); - }); - - await i.step('pin (failed)', async () => { - mockFetch('http://localhost:8000/posts/:id/pin', { - status: 404, - body: JSON.stringify({ - error: true, - type: 'notFound', - }) - }); - - await assertRejects(async () => { - await p.pin(); - }); - }); - - await i.step('unpin (failed)', async () => { - mockFetch('http://localhost:8000/posts/:id/pin', { - status: 404, - body: JSON.stringify({ - error: true, - type: 'notFound', - }) - }); - - await assertRejects(async () => { - await p.unpin(); - }); - }); + const p = new post({ + api_url: 'http://localhost:8000', + api_token: 'test', + data: regular_post, + }); + + await i.step('pin (successful)', async () => { + mockFetch('http://localhost:8000/posts/:id/pin', { + body: JSON.stringify({ + ...regular_post, + pinned: true, + }), + }); + + await p.pin(); + + assertEquals(p.pinned, true); + }); + + await i.step('unpin (successful)', async () => { + mockFetch('http://localhost:8000/posts/:id/pin', { + body: JSON.stringify({ + ...regular_post, + pinned: false, + }), + }); + + await p.unpin(); + + assertEquals(p.pinned, false); + }); + + await i.step('pin (failed)', async () => { + mockFetch('http://localhost:8000/posts/:id/pin', { + status: 404, + body: JSON.stringify({ + error: true, + type: 'notFound', + }), + }); + + await assertRejects(async () => { + await p.pin(); + }); + }); + + await i.step('unpin (failed)', async () => { + mockFetch('http://localhost:8000/posts/:id/pin', { + status: 404, + body: JSON.stringify({ + error: true, + type: 'notFound', + }), + }); + + await assertRejects(async () => { + await p.unpin(); + }); + }); }); Deno.test('post deletion', async (i) => { - const p = new post({ - api_url: 'http://localhost:8000', - api_token: 'test', - data: regular_post, - }); - - await i.step('delete (successful)', async () => { - mockFetch('http://localhost:8000/posts/?id=:id', { - status: 200 - }); - - // this will fail if the call fails - - await p.delete(); - }); - - await i.step('delete (failed)', async () => { - mockFetch('http://localhost:8000/posts/?id=:id', { - status: 404, - body: JSON.stringify({ - error: true, - type: 'notFound', - }) - }); - - await assertRejects(async () => { - await p.delete(); - }); - }); -}) + const p = new post({ + api_url: 'http://localhost:8000', + api_token: 'test', + data: regular_post, + }); + + await i.step('delete (successful)', async () => { + mockFetch('http://localhost:8000/posts/?id=:id', { + status: 200, + }); + + // this will fail if the call fails + + await p.delete(); + }); + + await i.step('delete (failed)', async () => { + mockFetch('http://localhost:8000/posts/?id=:id', { + status: 404, + body: JSON.stringify({ + error: true, + type: 'notFound', + }), + }); + + await assertRejects(async () => { + await p.delete(); + }); + }); +}); Deno.test('post reporting', async (i) => { - const p = new post({ - api_url: 'http://localhost:8000', - api_token: 'test', - data: regular_post, - }); - - await i.step('report (successful)', async () => { - mockFetch('http://localhost:8000/posts/:id/report', { - status: 200 - }); - - // this will fail if the call fails - - await p.report({ reason: 'test', comment: 'test' }); - }); - - await i.step('report (failed)', async () => { - mockFetch('http://localhost:8000/posts/:id/report', { - status: 404, - body: JSON.stringify({ - error: true, - type: 'notFound', - }) - }); - - await assertRejects(async () => { - await p.report({ reason: 'test', comment: 'test' }); - }); - }); -}) \ No newline at end of file + const p = new post({ + api_url: 'http://localhost:8000', + api_token: 'test', + data: regular_post, + }); + + await i.step('report (successful)', async () => { + mockFetch('http://localhost:8000/posts/:id/report', { + status: 200, + }); + + // this will fail if the call fails + + await p.report({ reason: 'test', comment: 'test' }); + }); + + await i.step('report (failed)', async () => { + mockFetch('http://localhost:8000/posts/:id/report', { + status: 404, + body: JSON.stringify({ + error: true, + type: 'notFound', + }), + }); + + await assertRejects(async () => { + await p.report({ reason: 'test', comment: 'test' }); + }); + }); +}); + +Deno.test('post editing', async (i) => { + const p = new post({ + api_url: 'http://localhost:8000', + api_token: 'test', + data: regular_post, + }); + + await i.step('edit (successful)', async () => { + mockFetch('http://localhost:8000/posts/?id=:id', { + body: JSON.stringify({ + ...regular_post, + p: 'new content', + }), + }); + + await p.update({ + content: 'new content', + }); + + assertEquals(p.content, 'new content'); + }); + + await i.step('edit (failed)', async () => { + mockFetch('http://localhost:8000/posts/?id=:id', { + status: 404, + body: JSON.stringify({ + error: true, + type: 'notFound', + }), + }); + + await assertRejects(async () => { + await p.update({ + content: 'new content', + }); + }); + }); +});