code: nextjs,frontends,node (4 files)

This commit is contained in:
2025-12-26 00:24:43 +00:00
parent c20f8b824d
commit 1ce9d5be1a
4 changed files with 130 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
const mockList = vi.fn()
const mockFindFirst = vi.fn()
const mockAdapter = { list: mockList, findFirst: mockFindFirst }
const mockVerifyPassword = vi.fn()
vi.mock('../dbal-client', () => ({
getAdapter: () => mockAdapter,
}))
vi.mock('../verify-password', () => ({
verifyPassword: mockVerifyPassword,
}))
import { authenticateUser } from './authenticate-user'
describe('authenticateUser', () => {
beforeEach(() => {
mockList.mockReset()
mockFindFirst.mockReset()
mockVerifyPassword.mockReset()
})
it('returns invalid credentials when credential is missing', async () => {
mockList.mockResolvedValue({ data: [] })
const result = await authenticateUser('alice', 'password')
expect(mockList).toHaveBeenCalledWith('Credential', { filter: { username: 'alice' } })
expect(mockVerifyPassword).not.toHaveBeenCalled()
expect(result).toEqual({ success: false, user: null, error: 'invalid_credentials' })
})
it('returns invalid credentials when password is wrong', async () => {
mockList.mockResolvedValue({ data: [{ username: 'alice', passwordHash: 'hash' }] })
mockVerifyPassword.mockResolvedValue(false)
const result = await authenticateUser('alice', 'password')
expect(mockVerifyPassword).toHaveBeenCalledWith('password', 'hash')
expect(mockFindFirst).not.toHaveBeenCalled()
expect(result).toEqual({ success: false, user: null, error: 'invalid_credentials' })
})
it('returns user_not_found when credential is valid but user missing', async () => {
mockList.mockResolvedValue({ data: [{ username: 'alice', passwordHash: 'hash' }] })
mockVerifyPassword.mockResolvedValue(true)
mockFindFirst.mockResolvedValue(null)
const result = await authenticateUser('alice', 'password')
expect(mockFindFirst).toHaveBeenCalledWith('User', { where: { username: 'alice' } })
expect(result).toEqual({ success: false, user: null, error: 'user_not_found' })
})
it.each([
{ firstLogin: true, expected: true },
{ firstLogin: false, expected: false },
])('returns requiresPasswordChange=$expected when firstLogin=$firstLogin', async ({ firstLogin, expected }) => {
mockList.mockResolvedValue({ data: [{ username: 'alice', passwordHash: 'hash' }] })
mockVerifyPassword.mockResolvedValue(true)
mockFindFirst.mockResolvedValue({
id: 'user_1',
username: 'alice',
email: 'alice@example.com',
role: 'user',
profilePicture: null,
bio: null,
createdAt: BigInt(1000),
tenantId: null,
isInstanceOwner: false,
firstLogin,
})
const result = await authenticateUser('alice', 'password')
expect(result).toEqual({
success: true,
user: {
id: 'user_1',
username: 'alice',
email: 'alice@example.com',
role: 'user',
profilePicture: undefined,
bio: undefined,
createdAt: 1000,
tenantId: undefined,
isInstanceOwner: false,
},
requiresPasswordChange: expected,
})
})
})

View File

@@ -0,0 +1,10 @@
import type { FileNode } from './types'
export function deleteNode(nodes: FileNode[], id: string): FileNode[] {
return nodes
.filter((node) => node.id !== id)
.map((node) => {
if (!node.children) return node
return { ...node, children: deleteNode(node.children, id) }
})
}

View File

@@ -0,0 +1,12 @@
import type { FileNode } from './types'
export function findNodeById(nodes: FileNode[], id: string): FileNode | null {
for (const node of nodes) {
if (node.id === id) return node
if (node.children) {
const found = findNodeById(node.children, id)
if (found) return found
}
}
return null
}

View File

@@ -0,0 +1,13 @@
import type { FileNode } from './types'
export function updateNode(nodes: FileNode[], id: string, updates: Partial<FileNode>): FileNode[] {
return nodes.map((node) => {
if (node.id === id) {
return { ...node, ...updates }
}
if (node.children) {
return { ...node, children: updateNode(node.children, id, updates) }
}
return node
})
}