Skip to content

Commit

Permalink
[#IOPAE-123] UpdateServiceLogo with Manage API Key (#263)
Browse files Browse the repository at this point in the history
  • Loading branch information
giuseppedipinto authored Feb 13, 2023
1 parent f09e530 commit 62346c6
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 7 deletions.
209 changes: 204 additions & 5 deletions UploadServiceLogo/__tests__/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { left, right } from "fp-ts/lib/Either";
import * as reporters from "@pagopa/ts-commons/lib/reporters";
import { Logo } from "@pagopa/io-functions-admin-sdk/Logo";
import { UploadServiceLogoHandler } from "../handler";
import { SubscriptionWithoutKeys } from "@pagopa/io-functions-admin-sdk/SubscriptionWithoutKeys";

const mockContext = {
// eslint-disable no-console
Expand All @@ -41,6 +42,9 @@ const anOrganizationFiscalCode = "01234567890" as OrganizationFiscalCode;
const anEmail = "test@example.com" as EmailString;

const aServiceId = "s123" as NonEmptyString;
const aManageSubscriptionId = "MANAGE-123" as NonEmptyString;
const aUserId = "u123" as NonEmptyString;
const aDifferentUserId = "u456" as NonEmptyString;
const someSubscriptionKeys = {
primary_key: "primary_key",
secondary_key: "secondary_key"
Expand Down Expand Up @@ -72,17 +76,43 @@ const aUserAuthenticationDeveloper: IAzureApiAuthorization = {
groups: new Set([UserGroup.ApiServiceRead, UserGroup.ApiServiceWrite]),
kind: "IAzureApiAuthorization",
subscriptionId: aServiceId,
userId: "u123" as NonEmptyString
userId: aUserId
};

const aUserAuthenticationDeveloperWithManageKey: IAzureApiAuthorization = {
...aUserAuthenticationDeveloper,
subscriptionId: aManageSubscriptionId
};

const aDifferentUserAuthenticationDeveloperWithManageKey: IAzureApiAuthorization = {
...aUserAuthenticationDeveloperWithManageKey,
userId: aDifferentUserId
};

const aLogoPayload: Logo = {
logo: "base64-logo-img" as NonEmptyString
};

const aRetrievedServiceSubscription: SubscriptionWithoutKeys = {
id: aServiceId,
owner_id: aUserId,
scope: "aScope"
};

const aRetrievedServiceSubscriptionWithoutOwnerId: SubscriptionWithoutKeys = {
id: aServiceId,
scope: "aScope"
};

describe("UploadServiceLogoHandler", () => {
it("should respond with 200 if log upload was successfull", async () => {
it("should respond with 200 if logo upload was successfull", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 })))
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 }))),
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
Expand All @@ -98,6 +128,7 @@ describe("UploadServiceLogoHandler", () => {
);

expect(apiClientMock.uploadServiceLogo).toHaveBeenCalledTimes(1);
expect(apiClientMock.getSubscription).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseSuccessJson");
if (result.kind === "IResponseSuccessJson") {
expect(result.value).toBeUndefined();
Expand All @@ -106,7 +137,12 @@ describe("UploadServiceLogoHandler", () => {

it("should respond with an Unauthorized error if service is no owned by current user", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 })))
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 }))),
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
Expand All @@ -121,12 +157,19 @@ describe("UploadServiceLogoHandler", () => {
aLogoPayload
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.uploadServiceLogo).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorForbiddenNotAuthorized");
});

it("should respond with an internal error if upload service logo does not respond", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.reject(new Error("Timeout")))
uploadServiceLogo: jest.fn(() => Promise.reject(new Error("Timeout"))),
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
Expand All @@ -141,6 +184,7 @@ describe("UploadServiceLogoHandler", () => {
aLogoPayload
);

expect(apiClientMock.getSubscription).not.toHaveBeenCalled();
expect(apiClientMock.uploadServiceLogo).toHaveBeenCalledTimes(1);

expect(result.kind).toBe("IResponseErrorInternal");
Expand All @@ -150,6 +194,11 @@ describe("UploadServiceLogoHandler", () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() =>
Promise.resolve(left({ err: "ValidationError" }))
),
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
)
};

Expand All @@ -169,6 +218,7 @@ describe("UploadServiceLogoHandler", () => {
aLogoPayload
);

expect(apiClientMock.getSubscription).not.toHaveBeenCalled();
expect(apiClientMock.uploadServiceLogo).toHaveBeenCalledTimes(1);

expect(result.kind).toBe("IResponseErrorInternal");
Expand Down Expand Up @@ -220,4 +270,153 @@ describe("UploadServiceLogoHandler", () => {

expect(result.kind).toBe("IResponseErrorForbiddenNotAuthorized");
});

// MANAGE Flow Tests -->
it("should respond with 200 if logo upload was successfull, using a MANAGE API Key", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 }))),
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
apiClientMock as any
);
const result = await uploadServiceLogoHandler(
mockContext,
aUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
aLogoPayload
);

expect(apiClientMock.uploadServiceLogo).toHaveBeenCalledTimes(1);
expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(result.kind).toBe("IResponseSuccessJson");
if (result.kind === "IResponseSuccessJson") {
expect(result.value).toBeUndefined();
}
});

it("should respond with an Unauthorized error if MANAGE API Key has a different ownerId", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 }))),
getSubscription: jest.fn(() =>
Promise.resolve(
right({ status: 200, value: aRetrievedServiceSubscription })
)
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
apiClientMock as any
);
const result = await uploadServiceLogoHandler(
mockContext,
aDifferentUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
aLogoPayload
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.uploadServiceLogo).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorForbiddenNotAuthorized");
});

it("should respond with an Unauthorized error if GetSubscription of a serviceId doesn't return an ownerId", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 }))),
getSubscription: jest.fn(() =>
Promise.resolve(
right({
status: 200,
value: aRetrievedServiceSubscriptionWithoutOwnerId
})
)
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
apiClientMock as any
);
const result = await uploadServiceLogoHandler(
mockContext,
aDifferentUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
aLogoPayload
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.uploadServiceLogo).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorForbiddenNotAuthorized");
});

it("should respond with an NotFound error if execute a GetSubscription of a not existing serviceId", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 }))),
getSubscription: jest.fn(() =>
Promise.resolve(
right({
status: 404,
value: {
error: {
code: "ResourceNotFound",
message: "Subscription not found.",
details: null
}
}
})
)
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
apiClientMock as any
);
const result = await uploadServiceLogoHandler(
mockContext,
aUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
aLogoPayload
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.uploadServiceLogo).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorNotFound");
});

it("should respond with an Error GetSubscription returns an error", async () => {
const apiClientMock = {
uploadServiceLogo: jest.fn(() => Promise.resolve(right({ status: 201 }))),
getSubscription: jest.fn(() =>
Promise.reject(new Error("Internal Server Error"))
)
};

const uploadServiceLogoHandler = UploadServiceLogoHandler(
apiClientMock as any
);
const result = await uploadServiceLogoHandler(
mockContext,
aUserAuthenticationDeveloperWithManageKey,
undefined as any, // not used
someUserAttributes,
aServiceId,
aLogoPayload
);

expect(apiClientMock.getSubscription).toHaveBeenCalledTimes(1);
expect(apiClientMock.uploadServiceLogo).not.toHaveBeenCalled();
expect(result.kind).toBe("IResponseErrorInternal");
});
});
24 changes: 22 additions & 2 deletions UploadServiceLogo/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
IResponseErrorNotFound,
IResponseErrorTooManyRequests,
IResponseSuccessJson,
ResponseErrorForbiddenNotAuthorized,
ResponseSuccessJson
} from "@pagopa/ts-commons/lib/responses";
import { NonEmptyString } from "@pagopa/ts-commons/lib/strings";
Expand All @@ -43,11 +44,16 @@ import { pipe } from "fp-ts/lib/function";
import { TaskEither } from "fp-ts/lib/TaskEither";
import * as TE from "fp-ts/lib/TaskEither";
import { Logo } from "@pagopa/io-functions-admin-sdk/Logo";
import { SequenceMiddleware } from "@pagopa/ts-commons/lib/sequence_middleware";
import { AzureUserAttributesManageMiddleware } from "@pagopa/io-functions-commons/dist/src/utils/middlewares/azure_user_attributes_manage";
import { APIClient } from "../clients/admin";
import { withApiRequestWrapper } from "../utils/api";
import { getLogger, ILogger } from "../utils/logging";
import { ErrorResponses, IResponseErrorUnauthorized } from "../utils/responses";
import { serviceOwnerCheckTask } from "../utils/subscription";
import {
serviceOwnerCheckManageTask,
serviceOwnerCheckTask
} from "../utils/subscription";

type ResponseTypes =
| IResponseSuccessJson<undefined>
Expand Down Expand Up @@ -104,6 +110,17 @@ export function UploadServiceLogoHandler(
return (_, apiAuth, ___, ____, serviceId, logoPayload) =>
pipe(
serviceOwnerCheckTask(serviceId, apiAuth.subscriptionId),
TE.fold(
__ =>
serviceOwnerCheckManageTask(
getLogger(_, logPrefix, "GetSubscription"),
apiClient,
serviceId,
apiAuth.subscriptionId,
apiAuth.userId
),
sid => TE.of(sid)
),
TE.chain(() =>
uploadServiceLogoTask(
getLogger(_, logPrefix, "UploadServiceLogo"),
Expand All @@ -129,7 +146,10 @@ export function UploadServiceLogo(
ContextMiddleware(),
AzureApiAuthMiddleware(new Set([UserGroup.ApiServiceWrite])),
ClientIpMiddleware,
AzureUserAttributesMiddleware(serviceModel),
SequenceMiddleware(ResponseErrorForbiddenNotAuthorized)(
AzureUserAttributesMiddleware(serviceModel),
AzureUserAttributesManageMiddleware()
),
RequiredParamMiddleware("service_id", NonEmptyString),
RequiredBodyPayloadMiddleware(Logo)
);
Expand Down

0 comments on commit 62346c6

Please sign in to comment.