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

statuscompliance / status-backend / 16137469197

08 Jul 2025 08:01AM UTC coverage: 86.231% (-0.5%) from 86.693%
16137469197

Pull #219

github

web-flow
Merge 030324b78 into 8d9115b5e
Pull Request #219: feat: implement selective control point calculation and pending contr…

873 of 1037 branches covered (84.19%)

Branch coverage included in aggregate %.

8 of 20 new or added lines in 4 files covered. (40.0%)

2 existing lines in 1 file now uncovered.

1751 of 2006 relevant lines covered (87.29%)

20.23 hits per line

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

72.33
/src/controllers/catalog.controller.js
1
import { models } from '../models/models.js';
2
import { storeGuaranteePoints } from '../utils/storeGuaranteePoints.js';
3
import registry from '../config/registry.js';
4
import { agreementBuilder } from '../utils/agreementBuilder.js';
5
import { v4 as uuidv4 } from 'uuid';
6
import _ from 'lodash';
7
import { finalizeControlsByCatalogId } from './control.controller.js';
8

9
export const getCatalogs = async (req, res) => {
54✔
10
  try {
3✔
11
    const { status } = req.query;
3✔
12

13
    let where = {};
3✔
14
    if (status === 'finalized' || status === 'draft') {
3✔
15
      where = { status };
1✔
16
    }
17

18
    const catalogs = await models.Catalog.findAll({ where });
3✔
19
    res.status(200).json(catalogs);
2✔
20
  } catch (error) {
21
    res.status(500).json({ message: `Failed to retrieve catalogs, error: ${error.message}` });
1✔
22
  }
23
};
24

25
export const getCatalog = async (req, res) => {
54✔
26
  try {
3✔
27
    const row = await models.Catalog.findByPk(req.params.id);
3✔
28

29
    if (!row) {
2✔
30
      return res.status(404).json({ message: 'Catalog not found' });
1✔
31
    }
32

33
    res.status(200).json(row);
1✔
34
  } catch (error) {
35
    res.status(500).json({ message: `Failed to retrieve catalog, error: ${error.message}` });
1✔
36
  }
37
};
38

39
export const createCatalog = async (req, res) => {
54✔
40
  try {
4✔
41
    const { name, description, startDate, endDate, dashboard_id, status } = req.body;
4✔
42
    if (!name || !startDate || !endDate) {
4✔
43
      return res.status(400).json({ message: 'Missing required fields: name, startDate, and/or endDate' });
1✔
44
    }
45
    const tpaId = status === 'draft' ? null : `tpa-${uuidv4()}`;
3✔
46
    const rows = await models.Catalog.create({
3✔
47
      name,
48
      description,
49
      startDate,
50
      endDate,
51
      dashboard_id,
52
      tpaId,
53
      status: status || 'finalized',
4✔
54
    });
55
    res.status(201).json(rows);
2✔
56
  } catch (error) {
57
    res.status(500).json({ message: `Failed to create catalog, error: ${error.message}` });
1✔
58
  }
59
};
60

61
export const updateCatalog = async (req, res) => {
54✔
62
  try {
4✔
63
    const { id } = req.params;
4✔
64
    const { name, description, startDate, endDate, dashboard_id, tpaId, status } = req.body;
4✔
65

66
    const currentCatalog = await models.Catalog.findByPk(id);
4✔
67
    if (!currentCatalog) {
4✔
68
      return res.status(404).json({ message: 'Catalog not found' });
1✔
69
    }
70

71
    // Prevent changing status from finalized to draft
72
    if (currentCatalog.status === 'finalized' && status === 'draft') {
3✔
73
      return res.status(400).json({ message: 'Cannot change status from finalized to draft' });
1✔
74
    }
75

76
    const updatedCatalog = await models.Catalog.update(
2✔
77
      {
78
        name,
79
        description,
80
        startDate,
81
        endDate,
82
        dashboard_id,
83
        tpaId,
84
        status,
85
      },
86
      {
87
        where: {
88
          id,
89
        },
90
        returning: true,
91
        plain: true,
92
      }
93
    );
94
    res.status(200).json(updatedCatalog[1]); // The first element is the number of affectedRows
1✔
95
  } catch (error) {
96
    res.status(500).json({ message: `Failed to update catalog, error: ${error.message}` });
1✔
97
  }
98
};
99

100
export const deleteCatalog = async (req, res) => {
54✔
101
  const result = await models.Catalog.destroy({
3✔
102
    where: {
103
      id: req.params.id,
104
    },
105
  });
106

107
  if (result <= 0)
2✔
108
    return res.status(404).json({
1✔
109
      message: 'Catalog not found',
110
    });
111

112
  res.sendStatus(204);
1✔
113
};
114

115
export async function calculatePoints(req, res) {
116

117
  try {
×
118
    const agreementId = req.params.tpaId;
×
119
    const { from, to } = req.query;
×
NEW
120
    const { controlIds } = req.body || {};
×
121

122
    // Validate agreementId format
123
    if (!/^tpa-[a-f0-9-]{36}$/.test(agreementId)) {
×
124
      return res.status(400).json({ message: 'Invalid agreementId format' });
×
125
    }
126

NEW
127
    const catalog = await models.Catalog.findOne({ where: { tpaId: agreementId } });
×
128

NEW
129
    let controls = [];
×
130

NEW
131
    if (Array.isArray(controlIds) && controlIds.length > 0) {
×
NEW
132
      controls = await models.Control.findAll({
×
133
        where: {
134
          catalogId: catalog.id,
135
          id: controlIds
136
        }
137
      });
138
    } else {
NEW
139
      controls = await models.Control.findAll({ where: { catalogId: catalog.id } });
×
140
    }
141

142
    await updateOrCreateAgreement(catalog, controls, agreementId);
×
143

144
    // Construct the URL for fetching guarantees
145
    const basePath = 'api/v6/states/';
×
146
    const safeAgreementId = encodeURIComponent(agreementId);
×
147

UNCOV
148
    const url = `${basePath}${safeAgreementId}/guarantees`;
×
149

150

UNCOV
151
    const guaranteesStates = await registry.get(url, {
×
152
      params: { from, to, newPeriodsFromGuarantees: false },
153
      headers: { 'x-access-token': req.cookies.accessToken }
154
    });
155

156
    // Update lastComputed
NEW
157
    const now = new Date();
×
NEW
158
    if (controls.length > 0) {
×
NEW
159
      const controlIdsToUpdate = controls.map(c => c.id);
×
160

NEW
161
      const [updatedCount] = await models.Control.update(
×
162
        { lastComputed: now },
163
        { where: { id: controlIdsToUpdate } }
164
      );
NEW
165
      res.status(200).json(updatedCount);
×
166
    }
167

168
    const { storedPoints, error } = await storeGuaranteePoints(guaranteesStates.data, agreementId);
×
169
    if (error.length > 0) {
×
NEW
170
      const points = await models.Point.findAll({ where: { agreementId } });
×
171
      if (points.length > 0) {
×
172
        res.status(200).json(points);
×
173
      } else {
174
        res.status(400).json(error);
×
175
      }
176
    } else {
177
      res.status(200).json(storedPoints);
×
178
    }
179
  } catch (error) {
180
    res.status(500).json({
×
181
      message: `Failed to get points, error: ${error.message}`,
182
    });
183
  }
184
}
185

186
export async function updateOrCreateAgreement(catalog, controls, agreementId) {
187
  const agreement = await agreementBuilder(catalog, controls, { id: agreementId });
4✔
188
  try {
4✔
189
    const response = await registry.get(`api/v6/agreements/${agreementId}`);
4✔
190

191
    const oldAgreement = response.data;
2✔
192

193
    if (!_.isEqual(agreement, oldAgreement)) {
2✔
194

195
      await registry.put(`api/v6/agreements/${agreementId}`, agreement);
1✔
196
    }
197
  } catch (error) {
198
    if (error.response?.status === 404) {
2✔
199

200
      await registry.post('api/v6/agreements', agreement);
1✔
201
    } else {
202
      throw error; // Rethrow other errors
1✔
203
    }
204
  }
205
}
206

207
// Draft Catalogs
208
export const createDraftCatalog = async (req, res) => {
54✔
209
  try {
3✔
210
    const { name, description, startDate, endDate, dashboard_id } = req.body;
3✔
211
    if (!name || !startDate) {
3✔
212
      return res.status(400).json({ message: 'Missing required fields: name and/or startDate' });
1✔
213
    }
214

215
    const rows = await models.Catalog.create({
2✔
216
      name,
217
      description,
218
      startDate: startDate,
219
      endDate,
220
      dashboard_id,
221
      tpaId: null,
222
      status: 'draft',
223
    });
224
    res.status(201).json(rows);
1✔
225
  } catch (error) {
226
    res.status(500).json({ message: `Failed to create draft catalog, error: ${error.message}` });
1✔
227
  }
228
};
229

230
export const finalizeCatalog = async (req, res) => {
54✔
231
  try {
5✔
232
    const { id } = req.params;
5✔
233

234
    const currentCatalog = await models.Catalog.findByPk(id);
5✔
235
    if (!currentCatalog) {
5✔
236
      return res.status(404).json({ message: 'Catalog not found' });
1✔
237
    }
238

239
    if (currentCatalog.status !== 'draft') {
4✔
240
      return res.status(400).json({ message: 'Only draft catalogs can be finalized' });
1✔
241
    }
242

243
    if (!currentCatalog.startDate || !currentCatalog.endDate) {
3✔
244
      return res.status(400).json({ message: 'Catalog must have startDate and endDate to be finalized' });
1✔
245
    }
246

247
    const tpaId = `tpa-${uuidv4()}`;
2✔
248

249
    // First we update the catalog status and TPA ID
250
    const updatedCatalog = await models.Catalog.update(
2✔
251
      {
252
        status: 'finalized',
253
        tpaId,
254
      },
255
      {
256
        where: {
257
          id,
258
        },
259
        returning: true,
260
        plain: true,
261
      }
262
    );
263

264
    // Then we finalize the controls
265
    const controlsResult = await finalizeControlsByCatalogId(id);
1✔
266

267
    // Return the updated catalog and the number of finalized controls
268
    const finalizedCount = Array.isArray(controlsResult?.updated) ? controlsResult.updated.length : 0;
1!
269
    res.status(200).json({
1✔
270
      catalog: updatedCatalog[1],
271
      controls: {
272
        finalized: finalizedCount,
273
      }
274
    });
275
  } catch (error) {
276
    res.status(500).json({ message: `Failed to finalize catalog, error: ${error.message}` });
1✔
277
  }
278
};
279

280
export { finalizeControlsByCatalogId } from './control.controller.js';
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