Skip to content

Commit 302c69b

Browse files
PIN-6540 Supertest in attribute registry process (#1758)
Co-authored-by: Tommaso Petrucciani <[email protected]>
1 parent fb50fec commit 302c69b

32 files changed

+1907
-694
lines changed

packages/attribute-registry-process/package.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"main": "dist",
66
"type": "module",
77
"scripts": {
8-
"test": "vitest",
8+
"test:api": "vitest --config vitest.api.config.ts",
9+
"test:integration": "vitest --config vitest.integration.config.ts",
910
"lint": "eslint . --ext .ts,.tsx",
1011
"lint:autofix": "eslint . --ext .ts,.tsx --fix",
1112
"format:check": "prettier --check src",
@@ -24,9 +25,13 @@
2425
"@protobuf-ts/runtime": "2.9.4",
2526
"@types/express": "4.17.21",
2627
"@types/node": "20.14.6",
28+
"@types/supertest": "6.0.2",
29+
"@types/jsonwebtoken": "^9.0.2",
2730
"pagopa-interop-commons-test": "workspace:*",
2831
"prettier": "2.8.8",
32+
"supertest": "7.0.0",
2933
"testcontainers": "10.9.0",
34+
"jsonwebtoken": "9.0.2",
3035
"tsx": "4.19.1",
3136
"typescript": "5.4.5",
3237
"vitest": "1.6.0"

packages/attribute-registry-process/src/app.ts

+25-12
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,34 @@ import { serviceName as modelsServiceName } from "pagopa-interop-models";
1212
import attributeRouter from "./routers/AttributeRouter.js";
1313
import healthRouter from "./routers/HealthRouter.js";
1414
import { config } from "./config/config.js";
15+
import { AttributeRegistryService } from "./services/attributeRegistryService.js";
1516

16-
const serviceName = modelsServiceName.ATTRIBUTE_REGISTRY_PROCESS;
17+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
18+
export async function createApp(service?: AttributeRegistryService) {
19+
const serviceName = modelsServiceName.ATTRIBUTE_REGISTRY_PROCESS;
1720

18-
const app = zodiosCtx.app();
21+
const router =
22+
service != null
23+
? attributeRouter(zodiosCtx, service)
24+
: attributeRouter(zodiosCtx);
1925

20-
// Disable the "X-Powered-By: Express" HTTP header for security reasons.
21-
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
22-
app.disable("x-powered-by");
26+
const app = zodiosCtx.app();
2327

24-
app.use(healthRouter);
25-
app.use(contextMiddleware(serviceName));
26-
app.use(await applicationAuditBeginMiddleware(serviceName, config));
27-
app.use(await applicationAuditEndMiddleware(serviceName, config));
28-
app.use(authenticationMiddleware(config));
29-
app.use(loggerMiddleware(serviceName));
30-
app.use(attributeRouter(zodiosCtx));
28+
// Disable the "X-Powered-By: Express" HTTP header for security reasons.
29+
// See https://cheatsheetseries.owasp.org/cheatsheets/HTTP_Headers_Cheat_Sheet.html#recommendation_16
30+
app.disable("x-powered-by");
31+
32+
app.use(healthRouter);
33+
app.use(contextMiddleware(serviceName));
34+
app.use(await applicationAuditBeginMiddleware(serviceName, config));
35+
app.use(await applicationAuditEndMiddleware(serviceName, config));
36+
app.use(authenticationMiddleware(config));
37+
app.use(loggerMiddleware(serviceName));
38+
app.use(router);
39+
40+
return app;
41+
}
42+
43+
const app = await createApp();
3144

3245
export default app;

packages/attribute-registry-process/src/routers/AttributeRouter.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import {
1919
} from "../model/domain/apiConverter.js";
2020
import { config } from "../config/config.js";
2121
import { makeApiProblem } from "../model/domain/errors.js";
22-
import { attributeRegistryServiceBuilder } from "../services/attributeRegistryService.js";
22+
import {
23+
AttributeRegistryService,
24+
attributeRegistryServiceBuilder,
25+
} from "../services/attributeRegistryService.js";
2326
import {
2427
createCertifiedAttributesErrorMapper,
2528
createDeclaredAttributesErrorMapper,
@@ -32,7 +35,7 @@ import {
3235

3336
const readModelRepository = ReadModelRepository.init(config);
3437
const readModelService = readModelServiceBuilder(readModelRepository);
35-
const attributeRegistryService = attributeRegistryServiceBuilder(
38+
const defaultAttributeRegistryService = attributeRegistryServiceBuilder(
3639
initDB({
3740
username: config.eventStoreDbUsername,
3841
password: config.eventStoreDbPassword,
@@ -46,7 +49,8 @@ const attributeRegistryService = attributeRegistryServiceBuilder(
4649
);
4750

4851
const attributeRouter = (
49-
ctx: ZodiosContext
52+
ctx: ZodiosContext,
53+
attributeRegistryService: AttributeRegistryService = defaultAttributeRegistryService
5054
): ZodiosRouter<ZodiosEndpointDefinitions, ExpressContext> => {
5155
const attributeRouter = ctx.router(attributeRegistryApi.attributeApi.api, {
5256
validationErrorHandler: zodiosValidationErrorToApiProblem,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import { describe, it, expect, vi } from "vitest";
3+
import { Attribute, generateId } from "pagopa-interop-models";
4+
import { generateToken, getMockAttribute } from "pagopa-interop-commons-test";
5+
import { AuthRole, authRole } from "pagopa-interop-commons";
6+
import request from "supertest";
7+
import { attributeRegistryApi } from "pagopa-interop-api-clients";
8+
import { api, attributeRegistryService } from "../vitest.api.setup.js";
9+
import { toApiAttribute } from "../../src/model/domain/apiConverter.js";
10+
import { attributeDuplicateByNameAndCode } from "../../src/model/domain/errors.js";
11+
12+
describe("API /certifiedAttributes authorization test", () => {
13+
const mockCertifiedAttributeSeed: attributeRegistryApi.CertifiedAttributeSeed =
14+
{
15+
name: "Certified Attribute",
16+
description: "This is a certified attribute",
17+
code: "001",
18+
};
19+
20+
const mockAttribute: Attribute = {
21+
...getMockAttribute(),
22+
id: generateId(),
23+
kind: "Certified",
24+
creationTime: new Date(),
25+
};
26+
27+
const apiAttribute = attributeRegistryApi.Attribute.parse(
28+
toApiAttribute(mockAttribute)
29+
);
30+
31+
attributeRegistryService.createCertifiedAttribute = vi
32+
.fn()
33+
.mockResolvedValue(mockAttribute);
34+
35+
const makeRequest = async (token: string) =>
36+
request(api)
37+
.post("/certifiedAttributes")
38+
.set("Authorization", `Bearer ${token}`)
39+
.set("X-Correlation-Id", generateId())
40+
.send(mockCertifiedAttributeSeed);
41+
42+
const authorizedRoles: AuthRole[] = [authRole.ADMIN_ROLE, authRole.M2M_ROLE];
43+
it.each(authorizedRoles)(
44+
"Should return 200 for user with role %s",
45+
async (role) => {
46+
const token = generateToken(role);
47+
const res = await makeRequest(token);
48+
expect(res.status).toBe(200);
49+
expect(res.body).toEqual(apiAttribute);
50+
}
51+
);
52+
53+
it.each(
54+
Object.values(authRole).filter((role) => !authorizedRoles.includes(role))
55+
)("Should return 403 for user with role %s", async (role) => {
56+
const token = generateToken(role);
57+
const res = await makeRequest(token);
58+
expect(res.status).toBe(403);
59+
});
60+
61+
it("Should return 409 for conflict", async () => {
62+
attributeRegistryService.createCertifiedAttribute = vi
63+
.fn()
64+
.mockRejectedValue(
65+
attributeDuplicateByNameAndCode(
66+
mockCertifiedAttributeSeed.name,
67+
mockCertifiedAttributeSeed.code
68+
)
69+
);
70+
71+
const res = await makeRequest(generateToken(authRole.ADMIN_ROLE));
72+
73+
expect(res.status).toBe(409);
74+
});
75+
76+
it("Should return 400 if passed an invalid certified attribute seed", async () => {
77+
const token = generateToken(authRole.ADMIN_ROLE);
78+
const res = await request(api)
79+
.post("/certifiedAttributes")
80+
.set("Authorization", `Bearer ${token}`)
81+
.set("X-Correlation-Id", generateId())
82+
.send({
83+
name: "Certified Attribute",
84+
code: "001",
85+
});
86+
expect(res.status).toBe(400);
87+
});
88+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import { describe, it, expect, vi } from "vitest";
3+
import { Attribute, generateId } from "pagopa-interop-models";
4+
import { generateToken, getMockAttribute } from "pagopa-interop-commons-test";
5+
import { AuthRole, authRole } from "pagopa-interop-commons";
6+
import request from "supertest";
7+
import { attributeRegistryApi } from "pagopa-interop-api-clients";
8+
import { api, attributeRegistryService } from "../vitest.api.setup.js";
9+
import { toApiAttribute } from "../../src/model/domain/apiConverter.js";
10+
import { attributeDuplicateByName } from "../../src/model/domain/errors.js";
11+
12+
describe("API /declaredAttributes authorization test", () => {
13+
const mockDeclaredAttributeSeed: attributeRegistryApi.AttributeSeed = {
14+
name: "Declared Attribute",
15+
description: "This is a declared attribute",
16+
};
17+
18+
const mockAttribute: Attribute = {
19+
...getMockAttribute(),
20+
id: generateId(),
21+
kind: "Declared",
22+
creationTime: new Date(),
23+
};
24+
25+
const apiAttribute = attributeRegistryApi.Attribute.parse(
26+
toApiAttribute(mockAttribute)
27+
);
28+
29+
attributeRegistryService.createDeclaredAttribute = vi
30+
.fn()
31+
.mockResolvedValue(mockAttribute);
32+
33+
const makeRequest = async (token: string) =>
34+
request(api)
35+
.post("/declaredAttributes")
36+
.set("Authorization", `Bearer ${token}`)
37+
.set("X-Correlation-Id", generateId())
38+
.send(mockDeclaredAttributeSeed);
39+
40+
const authorizedRoles: AuthRole[] = [authRole.ADMIN_ROLE, authRole.API_ROLE];
41+
42+
it.each(authorizedRoles)(
43+
"Should return 200 for user with role %s",
44+
async (role) => {
45+
const token = generateToken(role);
46+
const res = await makeRequest(token);
47+
48+
expect(res.status).toBe(200);
49+
expect(res.body).toEqual(apiAttribute);
50+
}
51+
);
52+
53+
it.each(
54+
Object.values(authRole).filter((role) => !authorizedRoles.includes(role))
55+
)("Should return 403 for user with role %s", async (role) => {
56+
const token = generateToken(role);
57+
const res = await makeRequest(token);
58+
expect(res.status).toBe(403);
59+
});
60+
61+
it("Should return 409 for conflict", async () => {
62+
attributeRegistryService.createDeclaredAttribute = vi
63+
.fn()
64+
.mockRejectedValue(
65+
attributeDuplicateByName(mockDeclaredAttributeSeed.name)
66+
);
67+
68+
const res = await makeRequest(generateToken(authRole.ADMIN_ROLE));
69+
70+
expect(res.status).toBe(409);
71+
});
72+
73+
it("Should return 400 if passed an invalid attribute seed", async () => {
74+
const token = generateToken(authRole.ADMIN_ROLE);
75+
const res = await request(api)
76+
.post("/declaredAttributes")
77+
.set("Authorization", `Bearer ${token}`)
78+
.set("X-Correlation-Id", generateId())
79+
.send({
80+
name: "Declared Attribute",
81+
});
82+
expect(res.status).toBe(400);
83+
});
84+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import { describe, it, expect, vi } from "vitest";
3+
import { Attribute, generateId } from "pagopa-interop-models";
4+
import { generateToken, getMockAttribute } from "pagopa-interop-commons-test";
5+
import { authRole } from "pagopa-interop-commons";
6+
import request from "supertest";
7+
import { attributeRegistryApi } from "pagopa-interop-api-clients";
8+
import { api, attributeRegistryService } from "../vitest.api.setup.js";
9+
import { toApiAttribute } from "../../src/model/domain/apiConverter.js";
10+
import { attributeDuplicateByNameAndCode } from "../../src/model/domain/errors.js";
11+
12+
describe("API /internal/certifiedAttributes authorization test", () => {
13+
const mockInternalCertifiedAttributeSeed: attributeRegistryApi.InternalCertifiedAttributeSeed =
14+
{
15+
code: "001",
16+
name: "Internal certified attribute",
17+
description: "description",
18+
origin: "IPA",
19+
};
20+
21+
const mockAttribute: Attribute = {
22+
...getMockAttribute(),
23+
id: generateId(),
24+
kind: "Certified",
25+
creationTime: new Date(),
26+
};
27+
28+
const apiAttribute = attributeRegistryApi.Attribute.parse(
29+
toApiAttribute(mockAttribute)
30+
);
31+
32+
attributeRegistryService.internalCreateCertifiedAttribute = vi
33+
.fn()
34+
.mockResolvedValue(mockAttribute);
35+
36+
const makeRequest = async (token: string) =>
37+
request(api)
38+
.post("/internal/certifiedAttributes")
39+
.set("Authorization", `Bearer ${token}`)
40+
.set("X-Correlation-Id", generateId())
41+
.send(mockInternalCertifiedAttributeSeed);
42+
43+
it("Should return 200 for user with role Internal", async () => {
44+
const token = generateToken(authRole.INTERNAL_ROLE);
45+
const res = await makeRequest(token);
46+
47+
expect(res.status).toBe(200);
48+
expect(res.body).toEqual(apiAttribute);
49+
});
50+
51+
it.each(
52+
Object.values(authRole).filter((role) => role !== authRole.INTERNAL_ROLE)
53+
)("Should return 403 for user with role %s", async (role) => {
54+
const token = generateToken(role);
55+
const res = await makeRequest(token);
56+
expect(res.status).toBe(403);
57+
});
58+
59+
it("Should return 409 for conflict", async () => {
60+
attributeRegistryService.internalCreateCertifiedAttribute = vi
61+
.fn()
62+
.mockRejectedValue(
63+
attributeDuplicateByNameAndCode(
64+
mockInternalCertifiedAttributeSeed.name,
65+
mockInternalCertifiedAttributeSeed.code
66+
)
67+
);
68+
69+
const res = await makeRequest(generateToken(authRole.INTERNAL_ROLE));
70+
71+
expect(res.status).toBe(409);
72+
});
73+
74+
it("Should return 400 if passed an invalid attribute seed", async () => {
75+
const token = generateToken(authRole.INTERNAL_ROLE);
76+
const res = await request(api)
77+
.post("/internal/certifiedAttributes")
78+
.set("Authorization", `Bearer ${token}`)
79+
.set("X-Correlation-Id", generateId())
80+
.send({
81+
code: "001",
82+
name: "Internal certified attribute",
83+
origin: "IPA",
84+
});
85+
expect(res.status).toBe(400);
86+
});
87+
});

0 commit comments

Comments
 (0)