Files
household-bot/apps/bot/src/server.test.ts

607 lines
17 KiB
TypeScript

import { describe, expect, test } from 'bun:test'
import { createBotWebhookServer } from './server'
describe('createBotWebhookServer', () => {
const server = createBotWebhookServer({
webhookPath: '/webhook/telegram',
webhookSecret: 'secret-token',
webhookHandler: async () => new Response('ok', { status: 200 }),
miniAppAuth: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppDashboard: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, dashboard: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppPendingMembers: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, members: [] }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppSettings: {
handler: async () =>
new Response(
JSON.stringify({
ok: true,
authorized: true,
settings: {},
assistantConfig: {},
categories: [],
members: []
}),
{
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
}
)
},
miniAppUpdateSettings: {
handler: async () =>
new Response(
JSON.stringify({ ok: true, authorized: true, settings: {}, assistantConfig: {} }),
{
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
}
)
},
miniAppUpsertUtilityCategory: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, category: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppPromoteMember: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, member: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppUpdateOwnDisplayName: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, member: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppUpdateMemberDisplayName: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, member: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppUpdateMemberRentWeight: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, member: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppUpdateMemberStatus: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, member: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppBillingCycle: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, cycleState: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppOpenCycle: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, cycleState: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppCloseCycle: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, cycleState: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppRentUpdate: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, cycleState: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppAddUtilityBill: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, cycleState: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppUpdateUtilityBill: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, cycleState: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppDeleteUtilityBill: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, cycleState: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
miniAppApproveMember: {
handler: async () =>
new Response(JSON.stringify({ ok: true, authorized: true, member: {} }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
},
scheduler: {
authorize: async (request) =>
request.headers.get('x-household-scheduler-secret') === 'scheduler-secret',
handler: async (_request, reminderType) =>
new Response(JSON.stringify({ ok: true, reminderType }), {
status: 200,
headers: {
'content-type': 'application/json; charset=utf-8'
}
})
}
})
test('returns health payload', async () => {
const response = await server.fetch(new Request('http://localhost/healthz'))
expect(response.status).toBe(200)
expect(await response.json()).toEqual({ ok: true })
})
test('rejects unknown path', async () => {
const response = await server.fetch(new Request('http://localhost/unknown'))
expect(response.status).toBe(404)
})
test('rejects webhook request with missing secret', async () => {
const response = await server.fetch(
new Request('http://localhost/webhook/telegram', {
method: 'POST'
})
)
expect(response.status).toBe(401)
})
test('rejects non-post method for webhook endpoint', async () => {
const response = await server.fetch(
new Request('http://localhost/webhook/telegram', {
method: 'GET',
headers: {
'x-telegram-bot-api-secret-token': 'secret-token'
}
})
)
expect(response.status).toBe(405)
})
test('accepts authorized webhook request', async () => {
const response = await server.fetch(
new Request('http://localhost/webhook/telegram', {
method: 'POST',
headers: {
'x-telegram-bot-api-secret-token': 'secret-token'
},
body: JSON.stringify({ update_id: 1 })
})
)
expect(response.status).toBe(200)
expect(await response.text()).toBe('ok')
})
test('accepts mini app auth request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/session', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true
})
})
test('accepts mini app dashboard request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/dashboard', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
dashboard: {}
})
})
test('accepts mini app pending members request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/pending-members', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
members: []
})
})
test('accepts mini app settings request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/settings', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
settings: {},
assistantConfig: {},
categories: [],
members: []
})
})
test('accepts mini app settings update request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/settings/update', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
settings: {},
assistantConfig: {}
})
})
test('accepts mini app utility category upsert request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/utility-categories/upsert', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
category: {}
})
})
test('accepts mini app promote member request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/members/promote', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
member: {}
})
})
test('accepts mini app own display name update request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/member/display-name', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
member: {}
})
})
test('accepts mini app member display name update request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/members/display-name', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
member: {}
})
})
test('accepts mini app rent weight update request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/members/rent-weight', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
member: {}
})
})
test('accepts mini app member status update request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/members/status', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
member: {}
})
})
test('accepts mini app billing cycle request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/billing-cycle', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
cycleState: {}
})
})
test('accepts mini app open cycle request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/billing-cycle/open', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
cycleState: {}
})
})
test('accepts mini app close cycle request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/billing-cycle/close', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
cycleState: {}
})
})
test('accepts mini app rent update request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/rent/update', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
cycleState: {}
})
})
test('accepts mini app utility bill add request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/utility-bills/add', {
method: 'POST',
body: JSON.stringify({ initData: 'payload' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
cycleState: {}
})
})
test('accepts mini app utility bill update request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/utility-bills/update', {
method: 'POST',
body: JSON.stringify({ initData: 'payload', billId: 'utility-1' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
cycleState: {}
})
})
test('accepts mini app utility bill delete request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/utility-bills/delete', {
method: 'POST',
body: JSON.stringify({ initData: 'payload', billId: 'utility-1' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
cycleState: {}
})
})
test('accepts mini app approve member request', async () => {
const response = await server.fetch(
new Request('http://localhost/api/miniapp/admin/approve-member', {
method: 'POST',
body: JSON.stringify({ initData: 'payload', pendingTelegramUserId: '123456' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
authorized: true,
member: {}
})
})
test('rejects scheduler request with missing secret', async () => {
const response = await server.fetch(
new Request('http://localhost/jobs/reminder/utilities', {
method: 'POST',
body: JSON.stringify({ period: '2026-03' })
})
)
expect(response.status).toBe(401)
})
test('rejects non-post method for scheduler endpoint', async () => {
const response = await server.fetch(
new Request('http://localhost/jobs/reminder/utilities', {
method: 'GET',
headers: {
'x-household-scheduler-secret': 'scheduler-secret'
}
})
)
expect(response.status).toBe(405)
})
test('accepts authorized scheduler request', async () => {
const response = await server.fetch(
new Request('http://localhost/jobs/reminder/rent-due', {
method: 'POST',
headers: {
'x-household-scheduler-secret': 'scheduler-secret'
},
body: JSON.stringify({ period: '2026-03' })
})
)
expect(response.status).toBe(200)
expect(await response.json()).toEqual({
ok: true,
reminderType: 'rent-due'
})
})
})