All files / msw / handlers projects.ts

41.93% Statements 26/62
42.85% Branches 30/70
53.33% Functions 8/15
42.1% Lines 24/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      222x                         222x 74x           65x                                               2x 1x   1x 1x       74x 18x                 16x 8x     8x     1x 1x     1x 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) {
      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 })
    }
 
    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 })
  }),
]