123 lines
4.9 KiB
TypeScript
123 lines
4.9 KiB
TypeScript
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||
|
|
import { prisma, createCaller } from '../setup'
|
||
|
|
import {
|
||
|
|
createTestUser, createTestProgram, createTestProject, cleanupTestData, uid,
|
||
|
|
} from '../helpers'
|
||
|
|
import { mentorRouter } from '../../src/server/routers/mentor'
|
||
|
|
import { signMentorUploadToken } from '../../src/lib/mentor-upload-token'
|
||
|
|
|
||
|
|
describe('mentor.workspace files end-to-end', () => {
|
||
|
|
let programId: string
|
||
|
|
let mentor: { id: string; email: string; role: 'MENTOR' }
|
||
|
|
let outsider: { id: string; email: string; role: 'JURY_MEMBER' }
|
||
|
|
let assignmentId: string
|
||
|
|
const userIds: string[] = []
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
process.env.NEXTAUTH_SECRET = process.env.NEXTAUTH_SECRET || 'test-secret-123'
|
||
|
|
const program = await createTestProgram({ name: `mentor-files-${uid()}` })
|
||
|
|
programId = program.id
|
||
|
|
const project = await createTestProject(programId, { title: 'Test Project' })
|
||
|
|
|
||
|
|
const m = await createTestUser('MENTOR')
|
||
|
|
userIds.push(m.id)
|
||
|
|
mentor = { id: m.id, email: m.email, role: 'MENTOR' }
|
||
|
|
const o = await createTestUser('JURY_MEMBER')
|
||
|
|
userIds.push(o.id)
|
||
|
|
outsider = { id: o.id, email: o.email, role: 'JURY_MEMBER' }
|
||
|
|
|
||
|
|
const assignment = await prisma.mentorAssignment.create({
|
||
|
|
data: {
|
||
|
|
id: uid('ma'), projectId: project.id, mentorId: m.id, method: 'MANUAL',
|
||
|
|
workspaceEnabled: true,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
assignmentId = assignment.id
|
||
|
|
})
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
await cleanupTestData(programId, userIds)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('issues an upload URL + signed token to the assigned mentor', async () => {
|
||
|
|
const caller = createCaller(mentorRouter, mentor)
|
||
|
|
const result = await caller.workspaceGetUploadUrl({
|
||
|
|
mentorAssignmentId: assignmentId,
|
||
|
|
fileName: 'plan.pdf',
|
||
|
|
mimeType: 'application/pdf',
|
||
|
|
size: 1024,
|
||
|
|
})
|
||
|
|
expect(typeof result.uploadUrl).toBe('string')
|
||
|
|
expect(result.uploadUrl).toContain('plan.pdf')
|
||
|
|
expect(typeof result.uploadToken).toBe('string')
|
||
|
|
expect(result.objectKey).toMatch(/^Test_Project\/mentorship\/\d+-plan\.pdf$/)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('rejects upload-url request from a user who is neither mentor nor team member', async () => {
|
||
|
|
const caller = createCaller(mentorRouter, outsider)
|
||
|
|
await expect(
|
||
|
|
caller.workspaceGetUploadUrl({
|
||
|
|
mentorAssignmentId: assignmentId, fileName: 'x.pdf', mimeType: 'application/pdf', size: 1,
|
||
|
|
})
|
||
|
|
).rejects.toThrow(/not a member|FORBIDDEN/i)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('records a file when given a valid token', async () => {
|
||
|
|
const caller = createCaller(mentorRouter, mentor)
|
||
|
|
const presign = await caller.workspaceGetUploadUrl({
|
||
|
|
mentorAssignmentId: assignmentId, fileName: 'a.pdf', mimeType: 'application/pdf', size: 99,
|
||
|
|
})
|
||
|
|
const file = await caller.workspaceUploadFile({
|
||
|
|
uploadToken: presign.uploadToken,
|
||
|
|
description: 'first file',
|
||
|
|
})
|
||
|
|
expect(file.fileName).toBe('a.pdf')
|
||
|
|
expect(file.objectKey).toBe(presign.objectKey)
|
||
|
|
expect(file.bucket).toBeTruthy()
|
||
|
|
})
|
||
|
|
|
||
|
|
it('rejects workspaceUploadFile with a token whose uploader differs from the caller', async () => {
|
||
|
|
const forged = signMentorUploadToken({
|
||
|
|
mentorAssignmentId: assignmentId,
|
||
|
|
uploaderUserId: 'someone-else',
|
||
|
|
fileName: 'x.pdf', mimeType: 'application/pdf', size: 1,
|
||
|
|
bucket: 'mopc-files', objectKey: 'a/mentorship/0-x.pdf',
|
||
|
|
exp: Math.floor(Date.now() / 1000) + 60,
|
||
|
|
})
|
||
|
|
const caller = createCaller(mentorRouter, mentor)
|
||
|
|
await expect(caller.workspaceUploadFile({ uploadToken: forged })).rejects.toThrow(/does not belong/i)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('lists files for the assigned mentor, sorted newest first', async () => {
|
||
|
|
const caller = createCaller(mentorRouter, mentor)
|
||
|
|
const a = await caller.workspaceGetUploadUrl({
|
||
|
|
mentorAssignmentId: assignmentId, fileName: 'b.pdf', mimeType: 'application/pdf', size: 50,
|
||
|
|
})
|
||
|
|
await caller.workspaceUploadFile({ uploadToken: a.uploadToken })
|
||
|
|
const files = await caller.workspaceGetFiles({ mentorAssignmentId: assignmentId })
|
||
|
|
expect(files.length).toBeGreaterThanOrEqual(2)
|
||
|
|
expect(new Date(files[0].createdAt).getTime()).toBeGreaterThanOrEqual(
|
||
|
|
new Date(files[1].createdAt).getTime(),
|
||
|
|
)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('refuses workspaceGetFiles to outsiders', async () => {
|
||
|
|
const caller = createCaller(mentorRouter, outsider)
|
||
|
|
await expect(
|
||
|
|
caller.workspaceGetFiles({ mentorAssignmentId: assignmentId })
|
||
|
|
).rejects.toThrow(/FORBIDDEN|not a member/i)
|
||
|
|
})
|
||
|
|
|
||
|
|
it('deletes a file the user uploaded', async () => {
|
||
|
|
const caller = createCaller(mentorRouter, mentor)
|
||
|
|
const p = await caller.workspaceGetUploadUrl({
|
||
|
|
mentorAssignmentId: assignmentId, fileName: 'kill.pdf', mimeType: 'application/pdf', size: 10,
|
||
|
|
})
|
||
|
|
const file = await caller.workspaceUploadFile({ uploadToken: p.uploadToken })
|
||
|
|
const result = await caller.workspaceDeleteFile({ mentorFileId: file.id })
|
||
|
|
expect(result.success).toBe(true)
|
||
|
|
const after = await prisma.mentorFile.findUnique({ where: { id: file.id } })
|
||
|
|
expect(after).toBeNull()
|
||
|
|
})
|
||
|
|
})
|