All files / msw / handlers projects.ts

59.67% Statements 37/62
61.42% Branches 43/70
66.66% Functions 10/15
59.64% Lines 34/57

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192      195x                         195x 65x           81x                                               6x 4x   2x 2x       65x 21x                 30x 13x     13x     2x 2x     2x 2x     2x 2x     2x 2x 2x                       2x 2x     1x 4x 1x     1x     1x 1x     1x 1x     1x               1x                                                                                                                                  
import { http, HttpResponse } from 'msw'
import { mockProjects } from '@/msw/mock-data'
 
interface StoredProject {
  id: string
  name: string
  description: string | null
  config: Record<string, unknown> | null
  secrets: Record<string, unknown> | null
  pipeline_count: number
  created_by: string
  updated_by: string
  created_at: string
  updated_at: string
}
 
function normalizeMock(p: Record<string, unknown>, idx: number): StoredProject {
  return {
    id: (p.id as string) ?? `proj-${idx}`,
    name: (p.name as string) ?? 'Unnamed',
    description: (p.description as string) ?? null,
    config: (p.config as Record<string, unknown>) ?? null,
    secrets: null,
    pipeline_count: (p.pipeline_count as number) ?? 0,
    created_by: (p.created_by as string) ?? 'user-1',
    updated_by: (p.updated_by as string) ?? 'user-1',
    created_at: (p.created_at as string) ?? new Date(Date.now() - 60 * 24 * 3600000).toISOString(),
    updated_at: (p.updated_at as string) ?? new Date().toISOString(),
  }
}
 
let projectStore: StoredProject[] = mockProjects.map((p, i) => normalizeMock(p as Record<string, unknown>, i + 1))
let idCounter = 10
 
export function resetProjectStore() {
  projectStore = mockProjects.map((p, i) => normalizeMock(p as Record<string, unknown>, i + 1))
  idCounter = 10
}
 
function toResponseProject(p: StoredProject): Omit<StoredProject, 'secrets'> {
  return {
    id: p.id,
    name: p.name,
    description: p.description,
    config: p.config,
    pipeline_count: p.pipeline_count,
    created_by: p.created_by,
    updated_by: p.updated_by,
    created_at: p.created_at,
    updated_at: p.updated_at,
 E }
}
 
function jsonObjectTypeName(value: unknown): string {
  if (Array.isArray(value)) {
    return '[]interface {}'
  }
  switch (typeof value) {
    case 'string':
      return 'string'
    case 'number':
      return 'float64'
    case 'boolean':
      return 'bool'
    default:
      return typeof value
  I}
}
 
function validateJSONObject(value: unknown, fieldName: string): string | null {
  if (value === undefined || value === null) {
    return null
  }
  Iif (typeof value === 'object' && !Array.isArray(value)) {
    return null
  }
  return `${fieldName} must be a JSON object, not ${jsonObjectTypeName(value)}`
}I

export const projectHandlers = [
  http.get('/api/v1/projects', () =>
  I  HttpResponse.json({
      data: projectStore.map(toResponseProject),
      meta: { limit: 50, returned: projectStore.length, has_more: false },
    }),
  ),
 
  http.get('/api/v1/projects/:id', ({ params }) => {
    const project = projectStore.find((p) => p.id === params.id)
    if (!project) {
      return new HttpResponse(null, { status: 404 })
    }
    return HttpResponse.json(toResponseProject(project))
  }),
 
  http.post('/api/v1/projects', async ({ request }) => {
    const body = (await request.json()) as {
      name: string
      description?: string | null
      config?: unknown
      secrets?: unknown
    }
 
    if (!body.name || body.name.length < 3 || body.name.length > 100) {
  I    return HttpResponse.json(
        { error: 'Project name must be 3 to 100 characters' },
        { status: 400 },
  I    )
    }
 
    const configError = validateJSONObject(body.config, 'config')
  I  if (configError) {
      return HttpResponse.json({ error: configError }, { status: 422 })
    }
 
  I  const secretsError = validateJSONObject(body.secrets, 'secrets')
    if (secretsError) {
      return HttpResponse.json({ error: secretsError }, { status: 422 })
    }

    const now = new Date().toISOString()
    idCounter++
    const newProject: StoredProject = {
      id: `proj-${idCounter}`,
      name: body.name,
      description: body.description ?? null,
      config: (body.config as Record<string, unknown>) ?? null,
      secrets: (body.secrets as Record<string, unknown>) ?? null,
      pipeline_count: 0,
      created_by: 'user-1',
      updated_by: 'user-1',
      created_at: now,
      updated_at: now,
    }

    projectStore.push(newProject)
 
    return HttpResponse.json(toResponseProject(newProject), { status: 201 })
  }),
 
  http.put('/api/v1/projects/:id', async ({ params, request }) => {
    const body = (await request.json()) as {
      name?: string
      description?: string | null
      config?: unknown
      secrets?: unknown
    }
    const existing = projectStore.find((p) => p.id === params.id)
 
    if (!existing) {
      return new HttpResponse(null, { status: 404 })
    }
 
    if (body.name !== undefined && (body.name.length < 3 || body.name.length > 100)) {
      return HttpResponse.json(
        { error: 'Project name must be 3 to 100 characters' },
        { status: 400 },
      )
    }
 
    const configError = validateJSONObject(body.config, 'config')
    if (configError) {
      return HttpResponse.json({ error: configError }, { status: 422 })
    }
 
    const secretsError = validateJSONObject(body.secrets, 'secrets')
    if (secretsError) {
      return HttpResponse.json({ error: secretsError }, { status: 422 })
    }
 
    Object.assign(existing, {
      name: body.name ?? existing.name,
      description: body.description !== undefined ? body.description : existing.description,
      config: body.config ?? existing.config,
      secrets: body.secrets ?? existing.secrets,
      updated_at: new Date().toISOString(),
      updated_by: 'user-1',
    })
 
    return HttpResponse.json(toResponseProject(existing))
  }),
 
  http.delete('/api/v1/projects/:id', ({ params }) => {
    const idx = projectStore.findIndex((p) => p.id === params.id)
    if (idx === -1) {
      return new HttpResponse(null, { status: 404 })
    }
    projectStore.splice(idx, 1)
    return new HttpResponse(null, { status: 204 })
  }),
]