• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

statuscompliance / status-backend / 14832753274

05 May 2025 08:55AM UTC coverage: 53.129% (+7.0%) from 46.084%
14832753274

Pull #165

github

web-flow
Merge 321b6d878 into abd751b2b
Pull Request #165: test: added panelUtils for fetching panel metadata with caching and r…

369 of 789 branches covered (46.77%)

Branch coverage included in aggregate %.

71 of 101 new or added lines in 2 files covered. (70.3%)

7 existing lines in 1 file now uncovered.

947 of 1688 relevant lines covered (56.1%)

7.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.66
/src/controllers/control.controller.js
1
import { models } from '../models/models.js';
2
import { checkRequiredProperties } from '../utils/checkRequiredProperties.js';
3
import { mapPanelsToDTO } from '../utils/panelUtils.js';
4

5
// Function to build the where clause for controls
6
const buildControlWhereClause = (query) => {
22✔
7
  const { status, ...otherFilters } = query;
6✔
8
  const whereClause = { ...otherFilters };
6✔
9

10
  if (status === 'finalized' || status === 'draft') {
6!
NEW
11
    whereClause.status = status;
×
12
  } else if (status) {
6✔
13
    throw new Error(
2✔
14
      `Invalid status filter: ${status}. Allowed values are "finalized" or "draft".`
15
    );
16
  }
17

18
  return whereClause;
4✔
19
};
20

21
// Function to check if a model instance exists by ID
22
export async function getModelById(res, model, id, { name = 'Resource' } = {}) {
×
23
  const entity = await model.findByPk(id);
22✔
24
  if (!entity) {
21✔
25
    res.status(404).json({ message: `${name} with ID ${id} not found.` });
6✔
26
    return null;
6✔
27
  }
28
  return entity;
15✔
29
}
30

31
export const getControls = async (req, res) => {
22✔
32
  try {
3✔
33
    const whereClause = buildControlWhereClause(req.query);
3✔
34
    const controls = await models.Control.findAll({ where: whereClause });
2✔
35
    res.status(200).json(controls);
1✔
36
  } catch (error) {
37
    if (error.message.startsWith('Invalid status filter')) {
2✔
38
      return res.status(400).json({ error: error.message });
1✔
39
    }
40
    res.status(500).json({
1✔
41
      message: 'Failed to retrieve controls',
42
      error: error.message,
43
    });
44
  }
45
};
46

47
export const getControl = async (req, res) => {
22✔
48
  try {
3✔
49
    const { id } = req.params;
3✔
50

51
    const control = await getModelById(res, models.Control, id, {
3✔
52
      name: 'Control',
53
    });
54
    if (!control) return; // aborts early with 404
2✔
55

56
    res.status(200).json(control);
1✔
57
  } catch (error) {
58
    res.status(500).json({
1✔
59
      message: 'Failed to retrieve control',
60
      error: error.message,
61
    });
62
  }
63
};
64

65
export const getCatalogControls = async (req, res) => {
22✔
66
  try {
3✔
67
    const { catalogId } = req.params;
3✔
68
    const query = { ...req.query, catalogId };
3✔
69
    const whereClause = buildControlWhereClause(query);
3✔
70
    const controls = await models.Control.findAll({ where: whereClause });
2✔
71

72
    res.status(200).json(controls);
1✔
73
  } catch (error) {
74
    if (error.message.startsWith('Invalid status filter')) {
2✔
75
      return res.status(400).json({ error: error.message });
1✔
76
    }
77
    res.status(500).json({
1✔
78
      message: 'Failed to retrieve catalog controls',
79
      error: error.message,
80
    });
81
  }
82
};
83

84
export const createControl = async (req, res) => {
22✔
85
  try {
4✔
86
    const {
87
      name,
88
      description,
89
      period,
90
      startDate,
91
      endDate,
92
      mashupId,
93
      catalogId,
94
      params, // Should include endpoint and threshold at least
95
    } = req.body;
4✔
96

97
    const { validation, textError } = checkRequiredProperties(params, [
4✔
98
      'endpoint',
99
      'threshold',
100
    ]);
101

102
    if (!validation) {
4✔
103
      return res
1✔
104
        .status(400)
105
        .json({ error: `Invalid parameters: ${textError}` });
106
    }
107

108
    let formattedStartDate = null;
3✔
109
    if (startDate) {
3!
110
      formattedStartDate = new Date(startDate);
3✔
111
      if (isNaN(formattedStartDate.getTime())) {
3✔
112
        return res.status(400).json({ error: 'Invalid startDate format' });
1✔
113
      }
114
    }
115
    const formattedEndDate = endDate ? new Date(endDate) : null;
2!
116

117
    const newControl = await models.Control.create({
2✔
118
      name,
119
      description,
120
      period,
121
      startDate: formattedStartDate,
122
      endDate: formattedEndDate,
123
      mashupId,
124
      catalogId,
125
      params,
126
      status: 'finalized',
127
    });
128
    res.status(201).json(newControl);
1✔
129
  } catch (error) {
130
    console.error('Error creating control:', error);
1✔
131
    res.status(500).json({
1✔
132
      message: 'Failed to create control',
133
      error: error.message,
134
    });
135
  }
136
};
137

138
export const updateControl = async (req, res) => {
22✔
139
  const { id } = req.params;
3✔
140
  const {
141
    name,
142
    description,
143
    period,
144
    startDate,
145
    endDate,
146
    mashupId,
147
    catalogId,
148
    params,
149
    status,
150
  } = req.body;
3✔
151

152
  try {
3✔
153
    const currentControl = await getModelById(res, models.Control, id, {
3✔
154
      name: 'Control',
155
    });
156
    if (!currentControl) return; // aborts early with 404
3✔
157

158
    if (currentControl.status === 'finalized' && status === 'draft') {
2✔
159
      return res.status(400).json({
1✔
160
        message: 'Cannot change status from finalized to draft',
161
      });
162
    }
163

164
    if (
1!
165
      status === 'finalized' ||
2!
166
      (!status && currentControl.status === 'finalized')
167
    ) {
NEW
UNCOV
168
      const { validation, textError } = checkRequiredProperties(
×
169
        params || currentControl.params,
×
170
        ['endpoint', 'threshold']
171
      );
UNCOV
172
      if (!validation) {
×
NEW
UNCOV
173
        return res
×
174
          .status(400)
175
          .json({
176
            error: `Invalid parameters for finalized control: ${textError}`,
177
          });
178
      }
179
    }
180

181
    const formattedStartDate = startDate
1!
182
      ? new Date(startDate)
183
      : currentControl.startDate;
184
    const formattedEndDate = endDate
1!
185
      ? new Date(endDate)
186
      : currentControl.endDate;
187

188
    await models.Control.update(
1✔
189
      {
190
        name,
191
        description,
192
        period,
193
        startDate: formattedStartDate,
194
        endDate: formattedEndDate,
195
        mashupId,
196
        catalogId,
197
        params,
198
        status,
199
      },
200
      {
201
        where: {
202
          id,
203
        },
204
      }
205
    );
206

207
    const control = await models.Control.findByPk(id);
1✔
208
    res.status(200).json(control);
1✔
209
  } catch (error) {
NEW
UNCOV
210
    res.status(500).json({
×
211
      message: 'Failed to update control',
212
      error: error.message,
213
    });
214
  }
215
};
216

217
export const deleteControl = async (req, res) => {
22✔
218
  try {
3✔
219
    const { id } = req.params;
3✔
220
    const deletedCount = await models.Control.destroy({
3✔
221
      where: { id },
222
    });
223

224
    if (deletedCount === 0) {
2✔
225
      return res
1✔
226
        .status(404)
227
        .json({ message: `Control with ID ${id} not found.` });
228
    }
229

230
    res.status(204).send(); // No content for successful deletion
1✔
231
  } catch (error) {
232
    res.status(500).json({
1✔
233
      message: 'Failed to delete control',
234
      error: error.message,
235
    });
236
  }
237
};
238

239
export async function addPanelToControl(req, res) {
240
  const { id, panelId } = req.params;
2✔
241
  const { dashboardUid } = req.body;
2✔
242

243
  try {
2✔
244
    const control = await getModelById(res, models.Control, id, {
2✔
245
      name: 'Control',
246
    });
247
    if (!control) return; // aborts early with 404
2✔
248

249
    const panel = await models.Panel.create({
1✔
250
      id: panelId,
251
      controlId: id,
252
      dashboardUid: dashboardUid,
253
    });
254
    res.status(201).json({
1✔
255
      message: 'Panel added to control successfully',
256
      data: panel,
257
    });
258
  } catch (error) {
UNCOV
259
    res.status(500).json({
×
260
      message: 'Failed to add panel to control',
261
      error: error.message,
262
    });
263
  }
264
}
265

266

267
export async function getPanelsByControlId(req, res) {
268
  const { id } = req.params;
3✔
269

270
  try {
3✔
271
    // Verify control exists
272
    const control = await getModelById(res, models.Control, id, {
3✔
273
      name: 'Control',
274
    });
275
    if (!control) return; // aborts early with 404
3✔
276
    // Fetch associated panels
277
    const panels = await models.Panel.findAll({
2✔
278
      where: {
279
        controlId: id,
280
      },
281
    });
282
    // Map to enriched DTOs
283
    const panelsDTO = await mapPanelsToDTO(panels);
2✔
284
   
285
    res.status(200).json(panelsDTO);
1✔
286
  } catch (error) {
287
    const message = 'Failed to get panels from control, error in Grafana API';
1✔
288
    const status = (error.response && error.response.status) || 500;
1!
289
    return res.status(status).json({ message, error: error.message });
1✔
290
  }
291
}
292

293
export async function deletePanelFromControl(req, res) {
294
  const { id, panelId } = req.params;
3✔
295

296
  try {
3✔
297
    const deletedCount = await models.Panel.destroy({
3✔
298
      where: {
299
        controlId: id,
300
        id: panelId,
301
      },
302
    });
303
    if (deletedCount === 0) {
2✔
304
      return res
1✔
305
        .status(404)
306
        .json({
307
          message: `Panel with ID ${panelId} not found for control ID ${id}.`,
308
        });
309
    }
310
    res.status(204).send(); // No content for successful deletion
1✔
311
  } catch (error) {
312
    res.status(500).json({
1✔
313
      message: 'Failed to delete panel from control',
314
      error: error.message,
315
    });
316
  }
317
}
318

319
// Draft controls
320

321
export const createDraftControl = async (req, res) => {
22✔
322
  const {
323
    name,
324
    description,
325
    startDate,
326
    endDate,
327
    period,
328
    mashupId,
329
    catalogId,
330
    params,
331
  } = req.body;
5✔
332

333
  if (!name || !catalogId) {
5✔
334
    return res.status(400).json({
1✔
335
      error: 'Missing required fields for draft control: name and catalogId',
336
    });
337
  }
338

339
  const { validation, textError } = checkRequiredProperties(params, [
4✔
340
    'endpoint',
341
    'threshold',
342
  ]);
343
  if (!validation) {
4✔
344
    return res.status(400).json({ error: textError });
1✔
345
  }
346

347
  try {
3✔
348
    // Check if catalog exists
349
    const catalog = await getModelById(res, models.Catalog, catalogId, {
3✔
350
      name: 'Catalog',
351
    });
352
    if (!catalog) return; // aborts early with 404
3✔
353

354
    // Check if catalog is a draft
355
    if (catalog.status !== 'draft') {
2✔
356
      return res.status(400).json({
1✔
357
        error: 'Draft controls can only be added to draft catalogs',
358
      });
359
    }
360

361
    const newControl = await models.Control.create({
1✔
362
      name,
363
      description: description || '',
1!
364
      period: period || 'MONTHLY',
1!
365
      startDate: startDate ? new Date(startDate) : new Date(),
1!
366
      endDate: endDate ? new Date(endDate) : null,
1!
367
      mashupId: mashupId || '',
1!
368
      catalogId,
369
      params: params || {},
1!
370
      status: 'draft',
371
    });
372

373
    res.status(201).json(newControl);
1✔
374
  } catch (error) {
UNCOV
375
    res.status(500).json({
×
376
      message: 'Failed to create draft control',
377
      error: error.message,
378
    });
379
  }
380
};
381

382
export const finalizeControl = async (req, res) => {
22✔
383
  try {
5✔
384
    const { id } = req.params;
5✔
385

386
    const currentControl = await getModelById(res, models.Control, id, {
5✔
387
      name: 'Control',
388
    });
389
    if (!currentControl) return; // aborts early with 404
5✔
390

391
    if (currentControl.status !== 'draft') {
4✔
392
      return res
1✔
393
        .status(400)
394
        .json({ message: 'Only draft controls can be finalized' });
395
    }
396

397
    // Check if associated catalog is finalized
398
    const catalog = await getModelById(
3✔
399
      res,
400
      models.Catalog,
401
      currentControl.catalogId,
402
      { name: 'Associated catalog' }
403
    );
404
    if (!catalog) return; // aborts early with 404
3!
405

406
    if (catalog.status !== 'finalized') {
3✔
407
      return res.status(400).json({
1✔
408
        message: 'Cannot finalize a control that belongs to a draft catalog',
409
      });
410
    }
411

412
    // Check required properties for finalized controls
413
    const { validation, textError } = checkRequiredProperties(
2✔
414
      currentControl.params,
415
      ['endpoint', 'threshold']
416
    );
417

418
    if (!validation) {
2✔
419
      return res.status(400).json({
1✔
420
        error: `Cannot finalize control: ${textError}`,
421
      });
422
    }
423

424
    const updatedControl = await models.Control.update(
1✔
425
      {
426
        status: 'finalized',
427
      },
428
      {
429
        where: {
430
          id,
431
        },
432
        returning: true,
433
      }
434
    );
435

436
    res.status(200).json(updatedControl[1][0]);
1✔
437
  } catch (error) {
NEW
UNCOV
438
    res.status(500).json({
×
439
      message: 'Failed to finalize control',
440
      error: error.message,
441
    });
442
  }
443
};
444

445
// Method to finalize all draft controls in a catalog
446
export const finalizeControlsByCatalogId = async (catalogId) => {
22✔
447
  try {
3✔
448
    // Get draft controls
449
    const draftControls = await models.Control.findAll({
3✔
450
      where: {
451
        catalogId,
452
        status: 'draft',
453
      },
454
    });
455

456
    // Update valid controls to finalized
457
    let updatedControls = {};
2✔
458
    if (draftControls.length > 0) {
2✔
459
      updatedControls = await models.Control.update(
1✔
460
        { status: 'finalized' },
461
        {
462
          where: {
463
            id: draftControls.map((control) => control.id),
2✔
464
          },
465
        }
466
      );
467
    }
468

469
    return updatedControls;
2✔
470
  } catch (error) {
471
    console.error(
1✔
472
      `Error finalizing controls for catalog ID ${catalogId}:`,
473
      error
474
    );
475
    throw error;
1✔
476
  }
477
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc