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

statuscompliance / status-backend / 17322953663

29 Aug 2025 11:47AM UTC coverage: 88.591% (+0.9%) from 87.721%
17322953663

push

github

web-flow
feat: implement and integrate hydrateControlsWithSecrets (#230)

Co-authored-by: Alvaro Jesus Bernal Caunedo <alvarobc2412@gmail.com>

1003 of 1160 branches covered (86.47%)

Branch coverage included in aggregate %.

84 of 86 new or added lines in 5 files covered. (97.67%)

2002 of 2232 relevant lines covered (89.7%)

21.92 hits per line

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

93.38
/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 { updateOrCreateAgreement } from '../utils/updateOrCreateAgreement.js';
5
import { v4 as uuidv4 } from 'uuid';
6
import { finalizeControlsByCatalogId } from './control.controller.js';
7
import { hydrateControlsWithSecrets } from '../utils/hydrateControlsWithSecrets.js';
8

9
export const getCatalogs = async (req, res) => {
62✔
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) => {
62✔
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) => {
62✔
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) => {
62✔
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) => {
62✔
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
  try {
5✔
117
    const userId = req.user?.user_id;
5✔
118
    const agreementId = req.params.tpaId;
5✔
119
    const { from, to, environment = 'production' } = req.query;
5✔
120
    const { controlIds } = req.body || {};
5✔
121

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

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

129
    const controls = Array.isArray(controlIds) && controlIds.length > 0
3✔
130
      ? await models.Control.findAll({ where: { catalogId: catalog.id, id: controlIds } })
131
      : await models.Control.findAll({ where: { catalogId: catalog.id } });
132

133
    // Hydrate controls with decrypted secrets
134
    const hydratedControls = await hydrateControlsWithSecrets(controls, {
3✔
135
      SecretModel: models.Secret,
136
      defaultEnvironment: environment,
137
      ownerId: userId,
138
    });
139

140
    await updateOrCreateAgreement(catalog, hydratedControls, agreementId);
3✔
141

142
    // Construct the URL for fetching guarantees
143
    const basePath = 'api/v6/states/';
1✔
144
    const safeAgreementId = encodeURIComponent(agreementId);
1✔
145
    const url = `${basePath}${safeAgreementId}/guarantees`;
1✔
146

147
    const guaranteesStates = await registry.get(url, {
1✔
148
      params: { from, to, newPeriodsFromGuarantees: false },
149
      headers: { 'x-access-token': req.cookies.accessToken }
150
    });
151

152
    // Update lastComputed
153
    const now = new Date();
1✔
154
    let updatedCount = 0;
1✔
155

156
    if (hydratedControls.length > 0) {
1!
157
      const controlIdsToUpdate = hydratedControls.map(c => c.id);
1✔
158

159
      const result = await models.Control.update(
1✔
160
        { lastComputed: now },
161
        { where: { id: controlIdsToUpdate } }
162
      );
163
      updatedCount = result?.[0] ?? 0;
1!
164
    }
165

166
    // Store guarantee points
167
    const { storedPoints, error } = await storeGuaranteePoints(guaranteesStates.data, agreementId);
1✔
168

169
    if (error.length > 0) {
1!
170
      const points = await models.Point.findAll({ where: { agreementId } });
×
171
      if (points.length > 0) {
×
NEW
172
        res.status(200).json({
×
173
          points,
174
          updatedCount,
175
          warnings: error
176
        });
177
      } else {
NEW
178
        res.status(400).json({
×
179
          message: 'Failed to store points and no existing points found',
180
          errors: error
181
        });
182
      }
183
    } else {
184
      res.status(200).json({
1✔
185
        storedPoints,
186
        updatedCount
187
      });
188
    }
189
  } catch (error) {
190
    console.error('calculatePoints error:', error);
3✔
191
    console.error(error.stack);
3✔
192
    res.status(500).json({
3✔
193
      message: `Failed to get points, error: ${error.message}`,
194
    });
195
  }
196
}
197

198
// Draft Catalogs
199
export const createDraftCatalog = async (req, res) => {
62✔
200
  try {
3✔
201
    const { name, description, startDate, endDate, dashboard_id } = req.body;
3✔
202
    if (!name || !startDate) {
3✔
203
      return res.status(400).json({ message: 'Missing required fields: name and/or startDate' });
1✔
204
    }
205

206
    const rows = await models.Catalog.create({
2✔
207
      name,
208
      description,
209
      startDate: startDate,
210
      endDate,
211
      dashboard_id,
212
      tpaId: null,
213
      status: 'draft',
214
    });
215
    res.status(201).json(rows);
1✔
216
  } catch (error) {
217
    res.status(500).json({ message: `Failed to create draft catalog, error: ${error.message}` });
1✔
218
  }
219
};
220

221
export const finalizeCatalog = async (req, res) => {
62✔
222
  try {
5✔
223
    const { id } = req.params;
5✔
224

225
    const currentCatalog = await models.Catalog.findByPk(id);
5✔
226
    if (!currentCatalog) {
5✔
227
      return res.status(404).json({ message: 'Catalog not found' });
1✔
228
    }
229

230
    if (currentCatalog.status !== 'draft') {
4✔
231
      return res.status(400).json({ message: 'Only draft catalogs can be finalized' });
1✔
232
    }
233

234
    if (!currentCatalog.startDate || !currentCatalog.endDate) {
3✔
235
      return res.status(400).json({ message: 'Catalog must have startDate and endDate to be finalized' });
1✔
236
    }
237

238
    const tpaId = `tpa-${uuidv4()}`;
2✔
239

240
    // First we update the catalog status and TPA ID
241
    const updatedCatalog = await models.Catalog.update(
2✔
242
      {
243
        status: 'finalized',
244
        tpaId,
245
      },
246
      {
247
        where: {
248
          id,
249
        },
250
        returning: true,
251
        plain: true,
252
      }
253
    );
254

255
    // Then we finalize the controls
256
    const controlsResult = await finalizeControlsByCatalogId(id);
1✔
257

258
    // Return the updated catalog and the number of finalized controls
259
    const finalizedCount = Array.isArray(controlsResult?.updated) ? controlsResult.updated.length : 0;
1!
260
    res.status(200).json({
1✔
261
      catalog: updatedCatalog[1],
262
      controls: {
263
        finalized: finalizedCount,
264
      }
265
    });
266
  } catch (error) {
267
    res.status(500).json({ message: `Failed to finalize catalog, error: ${error.message}` });
1✔
268
  }
269
};
270

271
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