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

statuscompliance / status-backend / 14592205578

22 Apr 2025 10:08AM UTC coverage: 45.53% (+9.6%) from 35.962%
14592205578

Pull #90

github

web-flow
Merge ac2816879 into ef0c8590e
Pull Request #90: test(utils): added dates

315 of 808 branches covered (38.99%)

Branch coverage included in aggregate %.

96 of 115 new or added lines in 1 file covered. (83.48%)

130 existing lines in 3 files now uncovered.

831 of 1709 relevant lines covered (48.62%)

5.31 hits per line

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

4.13
/src/controllers/control.controller.js
1
import { models } from '../models/models.js';
2
import { methods } from '../config/grafana.js';
3
import { checkRequiredProperties } from '../utils/checkRequiredProperties.js';
4

5
export const getControls = async (req, res) => {
17✔
6
  try {
×
7
    const { status } = req.query;
×
8
    
UNCOV
9
    const whereClause = {};
×
UNCOV
10
    if (status === 'finalized' || status === 'draft') {
×
11
      whereClause.status = status;
×
12
    }
13
    
14
    const rows = await models.Control.findAll({ 
×
15
      where: whereClause 
16
    });
17
    
18
    res.status(200).json(rows);
×
19
  } catch (error) {
UNCOV
20
    res.status(500).json({
×
21
      message: 'Error retrieving controls',
22
      error: error.message,
23
    });
24
  }
25
};
26

27
export const getControl = async (req, res) => {
17✔
28
  try {
×
UNCOV
29
    const row = await models.Control.findByPk(req.params.id);
×
30

UNCOV
31
    if (!row) {
×
32
      return res.status(404).json({
×
33
        message: 'Control not found',
34
      });
35
    }
36

UNCOV
37
    res.status(200).json(row);
×
38
  } catch (error) {
UNCOV
39
    res.status(500).json({
×
40
      message: 'Error retrieving control',
41
      error: error.message,
42
    });
43
  }
44
};
45

46
export const getCatalogControls = async (req, res) => {
17✔
47
  try {
×
UNCOV
48
    const { catalogId } = req.params;
×
UNCOV
49
    const { status } = req.query;
×
50
    
51
    const whereClause = {
×
52
      catalogId: catalogId,
53
    };
54
    
UNCOV
55
    if (status === 'finalized' || status === 'draft') {
×
UNCOV
56
      whereClause.status = status;
×
57
    }
58
    
59
    const rows = await models.Control.findAll({
×
60
      where: whereClause,
61
    });
62

UNCOV
63
    res.status(200).json(rows);
×
64
  } catch (error) {
UNCOV
65
    res.status(500).json({
×
66
      message: 'Error retrieving catalog controls',
67
      error: error.message,
68
    });
69
  }
70
};
71

72
export const createControl = async (req, res) => {
17✔
UNCOV
73
  try {
×
74
    const {
75
      name,
76
      description,
77
      period,
78
      startDate,
79
      endDate,
80
      mashupId,
81
      catalogId,
82
      params, // Should include endpoint and threshold at least
83
    } = req.body;
×
84
    
UNCOV
85
    const {validation, textError} = checkRequiredProperties(params, ['endpoint', 'threshold']);
×
86

UNCOV
87
    if(!validation) {
×
UNCOV
88
      return res.status(400).json({error: textError});
×
89
    }
90
    
91
    let formattedStartDate = null;
×
UNCOV
92
    if (startDate) {
×
UNCOV
93
      formattedStartDate = new Date(startDate);
×
UNCOV
94
      if (isNaN(formattedStartDate.getTime())) {
×
UNCOV
95
        return res.status(400).json({ error: 'Invalid startDate' });
×
96
      }
97
    }
UNCOV
98
    const formattedEndDate = endDate ? new Date(endDate) : null;
×
99

UNCOV
100
    const rows = await models.Control.create({
×
101
      name,
102
      description,
103
      period,
104
      startDate: formattedStartDate,
105
      endDate: formattedEndDate,
106
      mashupId,
107
      catalogId,
108
      params,
109
      status: 'finalized',
110
    });
111
    
112
    res.status(201).json({
×
113
      id: rows.id,
114
      name,
115
      description,
116
      period,
117
      formattedStartDate,
118
      formattedEndDate,
119
      mashupId,
120
      catalogId,
121
    });
122
  } catch (error) {
UNCOV
123
    console.error('Error creating control:', error);
×
UNCOV
124
    res.status(500).json({
×
125
      message: 'Error creating control',
126
      error: error.message,
127
    });
128
  }
129
};
130

131
export const updateControl = async (req, res) => {
17✔
UNCOV
132
  const { id } = req.params;
×
133
  const {
134
    name,
135
    description,
136
    period,
137
    startDate,
138
    endDate,
139
    mashupId,
140
    catalogId,
141
    params,
142
    status,
UNCOV
143
  } = req.body;
×
144
  
UNCOV
145
  try {
×
UNCOV
146
    const currentControl = await models.Control.findByPk(id);
×
147
    if (!currentControl) {
×
148
      return res.status(404).json({ message: 'Control not found' });
×
149
    }
150
    
151
    // No se permite cambiar un control finalizado a borrador
UNCOV
152
    if (currentControl.status === 'finalized' && status === 'draft') {
×
UNCOV
153
      return res.status(400).json({ 
×
154
        message: 'Cannot change status from finalized to draft' 
155
      });
156
    }
157
    
158
    // Si es un control finalizado, validar los params
159
    if (status === 'finalized' || (!status && currentControl.status === 'finalized')) {
×
UNCOV
160
      const {validation, textError} = checkRequiredProperties(params || currentControl.params, ['endpoint', 'threshold']);
×
UNCOV
161
      if (!validation) {
×
UNCOV
162
        return res.status(400).json({error: textError});
×
163
      }
164
    }
165
    
UNCOV
166
    const formattedStartDate = startDate ? new Date(startDate) : currentControl.startDate;
×
167
    const formattedEndDate = endDate ? new Date(endDate) : currentControl.endDate;
×
168

169
    await models.Control.update(
×
170
      {
171
        name,
172
        description,
173
        period,
174
        startDate: formattedStartDate,
175
        endDate: formattedEndDate,
176
        mashupId,
177
        catalogId,
178
        params,
179
        status,
180
      },
181
      {
182
        where: {
183
          id,
184
        },
185
      }
186
    );
187

188
    const row = await models.Control.findByPk(id);
×
UNCOV
189
    res.status(200).json(row);
×
190
  } catch (error) {
UNCOV
191
    res.status(500).json({ 
×
192
      message: `Failed to update control, error: ${error.message}` 
193
    });
194
  }
195
};
196

197
export const deleteControl = async (req, res) => {
17✔
UNCOV
198
  try {
×
UNCOV
199
    const { id } = req.params;
×
UNCOV
200
    const deletedCount = await models.Control.destroy({
×
201
      where: { id },
202
    });
203

204
    if (deletedCount === 0) {
×
UNCOV
205
      return res.status(404).json({ message: 'Control not found' });
×
206
    }
207

UNCOV
208
    res.status(204).json();
×
209
  } catch (error) {
UNCOV
210
    console.error('Error deleting control:', error);
×
UNCOV
211
    res.status(500).json({
×
212
      message: 'Error deleting control',
213
      error: error.message,
214
    });
215
  }
216
};
217

218
export async function addPanelToControl(req, res) {
219
  const { id, panelId } = req.params;
×
220
  const { dashboardUid } = req.body;
×
221

UNCOV
222
  try {
×
223
    const panel = await models.Panel.create({
×
224
      id: panelId,
225
      controlId: id,
226
      dashboardUid: dashboardUid,
227
    });
UNCOV
228
    res.status(201).json({
×
229
      message: 'Panel added to control',
230
      data: panel,
231
    });
232
  } catch (error) {
UNCOV
233
    res.status(500).json({
×
234
      message: 'Error adding panel to control',
235
      error: error.message,
236
    });
237
  }
238
}
239

240
export async function getPanelsByControlId(req, res) {
UNCOV
241
  const { id } = req.params;
×
242

UNCOV
243
  try {
×
244
    const panels = await models.Panel.findAll({
×
245
      where: {
246
        controlId: id,
247
      },
248
    });
UNCOV
249
    let panelsDTO = [];
×
250

251
    // THIS MUST BE CACHED AND REFACTORED
UNCOV
252
    for (let panel of panels) {
×
UNCOV
253
      panel = panel.dataValues;
×
254
      let panelDTO = {};
×
UNCOV
255
      if (Object.prototype.hasOwnProperty.call(panel, 'dashboardUid')) {
×
256
        const dashboardUid = panel.dashboardUid;
×
257
        const dashboardResponse =
UNCOV
258
                    await methods.dashboard.getDashboardByUID(dashboardUid);
×
UNCOV
259
        const actualDashboard = dashboardResponse.data.dashboard;
×
UNCOV
260
        const panelElement = actualDashboard.panels.find(
×
UNCOV
261
          (e) => e.id == panel.id
×
262
        );
263
        panelDTO = {
×
264
          ...panel,
265
          title: panelElement.title,
266
          type: panelElement.type,
267
          sqlQuery: panelElement.targets[0].rawSql,
268
          table: panelElement.targets[0].table,
269
          displayName: panelElement.targets[0].alias,
270
          gridPos: panelElement.gridPos,
271
        };
UNCOV
272
        panelsDTO.push(panelDTO);
×
273
      }
274
    }
UNCOV
275
    res.status(200).json(panelsDTO);
×
276
  } catch (error) {
277
    if (error.response) {
×
278
      const { status, statusText } = error.response;
×
UNCOV
279
      return res.status(status).json({
×
280
        message: statusText,
281
        error: error,
282
      });
283
    } else {
UNCOV
284
      res.status(500).json({
×
285
        message:
286
                    'Failed to get panels from control, error in Grafana API',
287
        error: error.message,
288
      });
289
    }
290
  }
291
}
292

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

296
  try {
×
297
    await models.Panel.destroy({
×
298
      where: {
299
        controlId: id,
300
        id: panelId,
301
      },
302
    });
UNCOV
303
    res.status(204).json({
×
304
      message: 'Panel deleted from control',
305
    });
306
  } catch (error) {
UNCOV
307
    res.status(500).json({
×
308
      message: 'Error deleting panel from control',
309
      error: error.message,
310
    });
311
  }
312
}
313

314
// Draft controls
315

316
export const createDraftControl = async (req, res) => {
17✔
317
  const {
318
    name,
319
    description,
320
    startDate,
321
    endDate,
322
    period,
323
    mashupId,
324
    catalogId,
325
    params,
326
  } = req.body;
×
327
  
328
  if (!name || !catalogId) {
×
329
    return res.status(400).json({
×
330
      error: 'Missing required fields for draft control: name and catalogId'
331
    });
332
  }
333
  
334
  try {
×
335
    // Check if catalog exists
336
    const catalog = await models.Catalog.findByPk(catalogId);
×
337
    if (!catalog) {
×
338
      return res.status(404).json({ error: 'Catalog not found' });
×
339
    }
340
    
341
    // Check if catalog is a draft
342
    if (catalog.status !== 'draft') {
×
343
      return res.status(400).json({
×
344
        error: 'Draft controls can only be added to draft catalogs'
345
      });
346
    }
347
    
348
    const rows = await models.Control.create({
×
349
      name,
350
      description: description || '',
×
351
      period: period || 'MONTHLY',
×
352
      startDate: startDate ? new Date(startDate) : new Date(),
×
353
      endDate: endDate ? new Date(endDate) : null,
×
354
      mashupId: mashupId || '',
×
355
      catalogId,
356
      params: params || {},
×
357
      status: 'draft',
358
    });
359
    
360
    res.status(201).json(rows);
×
361
  } catch (error) {
362
    res.status(500).json({
×
363
      message: `Failed to create draft control, error: ${error.message}`
364
    });
365
  }
366
};
367

368
export const finalizeControl = async (req, res) => {
17✔
369
  try {
×
370
    const { id } = req.params;
×
371
    
372
    const currentControl = await models.Control.findByPk(id);
×
373
    if (!currentControl) {
×
374
      return res.status(404).json({ message: 'Control not found' });
×
375
    }
376
    
377
    if (currentControl.status !== 'draft') {
×
378
      return res.status(400).json({ message: 'Only draft controls can be finalized' });
×
379
    }
380
    
381
    // Check if associated catalog is finalized
382
    const catalog = await models.Catalog.findByPk(currentControl.catalogId);
×
383
    if (!catalog) {
×
384
      return res.status(404).json({ message: 'Associated catalog not found' });
×
385
    }
386
    
387
    if (catalog.status !== 'finalized') {
×
388
      return res.status(400).json({ 
×
389
        message: 'Cannot finalize a control that belongs to a draft catalog' 
390
      });
391
    }
392
    
393
    // Check required properties for finalized controls
394
    const {validation, textError} = checkRequiredProperties(
×
395
      currentControl.params, 
396
      ['endpoint', 'threshold']
397
    );
398
    
399
    if (!validation) {
×
400
      return res.status(400).json({
×
401
        error: `Cannot finalize control: ${textError}`
402
      });
403
    }
404
    
405
    const updatedControl = await models.Control.update(
×
406
      {
407
        status: 'finalized',
408
      },
409
      {
410
        where: {
411
          id,
412
        },
413
        returning: true,
414
      }
415
    );
416
    
417
    res.status(200).json(updatedControl[1][0]);
×
418
  } catch (error) {
419
    res.status(500).json({ 
×
420
      message: `Failed to finalize control, error: ${error.message}` 
421
    });
422
  }
423
};
424

425
// Method to finalize all draft controls in a catalog
426
export const finalizeControlsByCatalogId = async (catalogId) => {
17✔
427
  try {
×
428
    // Get draft controls
429
    const draftControls = await models.Control.findAll({
×
430
      where: {
431
        catalogId,
432
        status: 'draft'
433
      }
434
    });
435
    
436
    // Update valid controls to finalized
437
    let updatedControls = {};
×
438
    if (draftControls.length > 0) {
×
439
      updatedControls = await models.Control.update(
×
440
        { status: 'finalized' },
441
        {
442
          where: {
443
            id: draftControls.map(control => control.id)
×
444
          }
445
        }
446
      );
447
    }
448
    
449
    return updatedControls;
×
450
  } catch (error) {
451
    console.error('Error finalizing controls:', error);
×
452
    throw error;
×
453
  }
454
};
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