4
0

files.ts 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
  1. import { randomUUID } from 'node:crypto'
  2. import { existsSync } from 'node:fs'
  3. import { mkdir, rm, writeFile } from 'node:fs/promises'
  4. import path from 'node:path'
  5. /**
  6. * Real files in the app server's upload folder, for the specs that check
  7. * what deleting something does to the disk. The server and the suite share
  8. * a disk locally and on CI (the server runs from the repository, and so
  9. * `data/uploads` is the same folder for both); a spec skips when they do not.
  10. */
  11. /** Where the app server keeps a workshop's uploads, as the suite sees it. */
  12. export function uploadPath(organizationId: string, ...segments: string[]): string {
  13. return path.join('data', 'uploads', organizationId, ...segments)
  14. }
  15. /** Whether this run can see the server's uploads at all. */
  16. export function sharesDiskWithServer(): boolean {
  17. return existsSync(path.join('data', 'uploads'))
  18. }
  19. export interface PlantedFile {
  20. /** What a row stores. */
  21. url: string
  22. /** Where it is on disk. */
  23. path: string
  24. }
  25. /** A file with a name like the upload routes give, and the URL a row would hold. */
  26. export async function plantFile(
  27. organizationId: string,
  28. folder: string,
  29. ext = 'jpg'
  30. ): Promise<PlantedFile> {
  31. const name = `${randomUUID()}.${ext}`
  32. const file = uploadPath(organizationId, folder, name)
  33. await mkdir(path.dirname(file), { recursive: true })
  34. await writeFile(file, `e2e ${folder} ${name}`)
  35. return { url: `/api/protected/files/${organizationId}/${folder}/${name}`, path: file }
  36. }
  37. export function onDisk(file: PlantedFile): boolean {
  38. return existsSync(file.path)
  39. }
  40. export async function removePlanted(files: PlantedFile[]): Promise<void> {
  41. await Promise.all(files.map((file) => rm(file.path, { force: true })))
  42. }