diff --git a/src/utils/__tests__/bokante-mastodon.test.js b/src/utils/__tests__/bokante-mastodon.test.js new file mode 100644 index 0000000..8bf735a --- /dev/null +++ b/src/utils/__tests__/bokante-mastodon.test.js @@ -0,0 +1,73 @@ +import {describe, it, expect, vi, afterEach, beforeEach} from 'vitest'; +import {createStatus, getStatusContext} from '../bokante-mastodon.js'; + +describe('bokante-mastodon', () => { + const originalFetch = global.fetch; + const originalEnv = {...process.env}; + + beforeEach(() => { + process.env.BOKANTE_INSTANCE_URL = 'https://bokante.o-k-i.net'; + process.env.BOKANTE_ACCESS_TOKEN = 'test-token'; + }); + + afterEach(() => { + global.fetch = originalFetch; + process.env = {...originalEnv}; + vi.restoreAllMocks(); + }); + + describe('createStatus', () => { + it('poste le statut avec le bon endpoint, jeton et corps', async () => { + global.fetch = vi.fn(async () => ({ + ok: true, + json: async () => ({id: '112233', url: 'https://bokante.o-k-i.net/@paroles/112233'}) + })); + + const result = await createStatus('Nouvelle parole : "Titre"'); + + const [url, options] = global.fetch.mock.calls[0]; + expect(url).toBe('https://bokante.o-k-i.net/api/v1/statuses'); + expect(options.method).toBe('POST'); + expect(options.headers.Authorization).toBe('Bearer test-token'); + expect(JSON.parse(options.body)).toEqual({ + status: 'Nouvelle parole : "Titre"', + visibility: 'public' + }); + expect(result).toEqual({id: '112233', url: 'https://bokante.o-k-i.net/@paroles/112233'}); + }); + + it('lève une erreur claire sur réponse HTTP non-ok', async () => { + global.fetch = vi.fn(async () => ({ + ok: false, + status: 422, + text: async () => 'Validation Failed' + })); + + await expect(createStatus('texte')).rejects.toThrow('Mastodon 422: Validation Failed'); + }); + }); + + describe('getStatusContext', () => { + it('récupère le contexte du statut avec le bon endpoint et jeton', async () => { + const context = {ancestors: [], descendants: [{id: '1', content: '

coucou

'}]}; + global.fetch = vi.fn(async () => ({ok: true, json: async () => context})); + + const result = await getStatusContext('112233'); + + const [url, options] = global.fetch.mock.calls[0]; + expect(url).toBe('https://bokante.o-k-i.net/api/v1/statuses/112233/context'); + expect(options.headers.Authorization).toBe('Bearer test-token'); + expect(result).toEqual(context); + }); + + it('lève une erreur claire sur réponse HTTP non-ok', async () => { + global.fetch = vi.fn(async () => ({ + ok: false, + status: 404, + text: async () => 'Record not found' + })); + + await expect(getStatusContext('inconnu')).rejects.toThrow('Mastodon 404: Record not found'); + }); + }); +}); diff --git a/src/utils/bokante-mastodon.js b/src/utils/bokante-mastodon.js new file mode 100644 index 0000000..362ef8f --- /dev/null +++ b/src/utils/bokante-mastodon.js @@ -0,0 +1,44 @@ +'use strict'; + +function getConfig() { + return { + instanceUrl: process.env.BOKANTE_INSTANCE_URL || 'https://bokante.o-k-i.net', + accessToken: process.env.BOKANTE_ACCESS_TOKEN + }; +} + +async function mastodonFetch(url, options) { + const {accessToken} = getConfig(); + const response = await fetch(url, { + ...options, + headers: { + Authorization: `Bearer ${accessToken}`, + ...options.headers + } + }); + + if (!response.ok) { + const body = await response.text(); + throw new Error(`Mastodon ${response.status}: ${body}`); + } + + return response.json(); +} + +async function createStatus(text, {visibility = 'public'} = {}) { + const {instanceUrl} = getConfig(); + return mastodonFetch(`${instanceUrl}/api/v1/statuses`, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({status: text, visibility}) + }); +} + +async function getStatusContext(statusId) { + const {instanceUrl} = getConfig(); + return mastodonFetch(`${instanceUrl}/api/v1/statuses/${statusId}/context`, { + method: 'GET' + }); +} + +module.exports = {createStatus, getStatusContext};