Files
remark42/frontend/packages/api/tests/clients/public.test.ts
T
Dmitry VerkhoturovandGitHub a4c5e17bbb Probe /auth/status from frontend to avoid 401 on /user (closes #1188) (#1763)
* Probe /auth/status from frontend to avoid 401 console noise on /user

GET /api/v1/user requires auth and returns 401 for anonymous visitors,
which the browser logs to console even when JS catches it. Probe
/auth/status first (always 200), then fetch /user only when logged in.
Stale auth cookies are cleared when status reports "not logged in" to
preserve the cleanup-on-probe behaviour previously triggered by /user 401.

Closes #1188.

* Don't clear auth cookies when /auth/status probe itself fails

A transient network/5xx on the /auth/status probe used to fall through
into the cookie-clear branch and silently log the user out on the next
page load. Distinguish "probe failed" (null) from explicit "not logged
in"; only the latter clears JWT/XSRF cookies. Lock the distinction with
a negative assertion in the probe-failure test.

Also align packages/api prettier config with apps/remark42 (trailingComma: 'es5')
so future edits don't sweep unrelated trailing commas into the diff.
2026-04-30 19:32:49 -05:00

117 lines
4.3 KiB
TypeScript

import { beforeEach, describe, expect } from 'vitest'
import { mockEndpoint } from '../test-utils'
import { createPublicClient, GetUserCommentsParams, Vote } from '../../clients/public'
interface Context {
client: ReturnType<typeof createPublicClient>
}
describe<Context>('Public Client', (publicClient) => {
beforeEach<Context>((ctx) => {
ctx.client = createPublicClient({ siteId: 'mysite', baseUrl: '/remark42' })
})
publicClient('getConfig: should return config', async ({ client }) => {
const data = { x: 1, y: 2 }
mockEndpoint('/remark42/api/v1/config', { body: data })
await expect(client.getConfig()).resolves.toEqual(data)
})
publicClient('getComments: should return page comments', async ({ client }) => {
const data = { post: { id: '1' }, node: [{ id: 1 }] }
const ref = mockEndpoint('/remark42/api/v1/comments', { body: data })
await expect(client.getComments('/post/1')).resolves.toEqual(data)
expect(ref.req.url.searchParams.get('url')).toBe('/post/1')
})
const commentRequestCases: GetUserCommentsParams[] = [
{ url: '' },
{ url: '' },
{ url: '', limit: 10 },
{ url: '', skip: 10 },
{ url: '', skip: 10, limit: 0 },
]
commentRequestCases.forEach((params) => {
publicClient(
`getComments: should return user comments with params: ${JSON.stringify(params)}`,
async ({ client }) => {
const data = [{ id: 1 }, { id: 2 }]
const ref = mockEndpoint('/remark42/api/v1/find', { body: data })
await expect(client.getComments(params)).resolves.toEqual(data)
expect(ref.req.url.searchParams.get('limit')).toBe(
params.limit === undefined ? null : `${params.limit}`
)
expect(ref.req.url.searchParams.get('skip')).toBe(
params.skip === undefined ? null : `${params.skip}`
)
}
)
})
publicClient('addComment: should add comment', async ({ client }) => {
const data = { id: '1', text: 'test' }
const ref = mockEndpoint('/remark42/api/v1/comment', { method: 'post', body: data })
await expect(client.addComment('/post/my-first-post', { text: 'test' })).resolves.toEqual(data)
await expect(ref.req.json()).resolves.toEqual({
text: data.text,
locator: {
site: 'mysite',
url: '/post/my-first-post',
},
})
})
publicClient('updateComment: should update comment', async ({ client }) => {
const data = { id: 1, body: 'test' }
const ref = mockEndpoint('/remark42/api/v1/comment/1', { method: 'put', body: data })
await expect(client.updateComment('/post/my-first-post', '1', 'test')).resolves.toEqual(data)
await expect(ref.req.json()).resolves.toEqual({ text: 'test' })
expect(ref.req.url.searchParams.get('url')).toBe('/post/my-first-post')
})
publicClient('should remove comment', async ({ client }) => {
const ref = mockEndpoint('/remark42/api/v1/comment/1', { method: 'put' })
await expect(client.removeComment('/post/my-first-post', '1')).resolves.toBe('')
expect(ref.req.url.searchParams.get('url')).toBe('/post/my-first-post')
})
const voteRequestCases: { vote: Vote; value: string }[] = [
{ vote: 1, value: 'upvote' },
{ vote: -1, value: 'downvote' },
]
voteRequestCases.forEach(({ vote, value }) => {
publicClient(`vote: should ${value} for comment`, async ({ client }) => {
const data = { id: 1, vote: 2 }
const ref = mockEndpoint('/remark42/api/v1/vote/1', { method: 'put', body: data })
await expect(client.vote('/post/my-first-post', '1', vote)).resolves.toEqual(data)
expect(ref.req.url.searchParams.get('url')).toBe('/post/my-first-post')
expect(ref.req.url.searchParams.get('vote')).toBe(`${vote}`)
})
})
publicClient('getUser: should return user when logged in', async ({ client }) => {
const user = { id: '1', username: 'user' }
mockEndpoint('/remark42/auth/status', { body: { status: 'logged in', user: 'user' } })
mockEndpoint('/remark42/api/v1/user', { body: user })
await expect(client.getUser()).resolves.toEqual(user)
})
publicClient('getUser: should return null when not logged in', async ({ client }) => {
mockEndpoint('/remark42/auth/status', { body: { status: 'not logged in' } })
await expect(client.getUser()).resolves.toBeNull()
})
publicClient('getUser: should return null when status probe fails', async ({ client }) => {
mockEndpoint('/remark42/auth/status', { status: 500, body: 'boom' })
await expect(client.getUser()).resolves.toBeNull()
})
})