Skip to content

Commit

Permalink
Adds tests
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudify committed Jul 25, 2019
1 parent d7ab43b commit 70d9b61
Show file tree
Hide file tree
Showing 8 changed files with 2,916 additions and 54 deletions.
4 changes: 2 additions & 2 deletions .circleci/config.yml
Expand Up @@ -40,7 +40,7 @@ jobs:
- checkout
- *restore_node_cache
- *install_node_modules
- run: yarn test
- run: yarn test:coverage
- run: 'bash <(curl -s https://codecov.io/bash)'

lint:
Expand Down Expand Up @@ -69,4 +69,4 @@ workflows:
- danger:
filters:
branches:
ignore: master
ignore: master
4 changes: 3 additions & 1 deletion .gitignore
Expand Up @@ -40,4 +40,6 @@ venv.bak/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*$py.class

coverage
196 changes: 196 additions & 0 deletions CreateMessage/__tests__/handler.test.ts
@@ -0,0 +1,196 @@
import * as fc from "fast-check";

import { MessageModel } from "io-functions-commons/dist/src/models/message";
import { UserGroup } from "io-functions-commons/dist/src/utils/middlewares/azure_api_auth";

import { WithinRangeInteger } from "italia-ts-commons/lib/numbers";
import { NonEmptyString } from "italia-ts-commons/lib/strings";

import { right } from "fp-ts/lib/Either";

import {
canDefaultAddresses,
canPaymentAmount,
canWriteMessage,
createMessageDocument
} from "../handler";

import {
fiscalCodeArb,
fiscalCodeArrayArb,
fiscalCodeSetArb,
maxAmountArb,
newMessageWithDefaultEmailArb,
newMessageWithPaymentDataArb,
upperCaseAlphaArb
} from "../../utils/arbitraries";

//
// tests
//

describe("canWriteMessage", () => {
it("should respond with ResponseErrorForbiddenNotAuthorizedForProduction when service is in no group", () => {
fc.assert(
fc.property(
fiscalCodeArrayArb,
fiscalCodeArb,
fc.boolean(),
(authorizedRecipients, recipient, isAuthorized) => {
const response = canWriteMessage(
new Set(), // no groups
new Set(
authorizedRecipients.concat(isAuthorized ? [recipient] : [])
), // any authorized recipient, possibly also the current one
recipient // one random recipient
);
expect(response.isLeft()).toBeTruthy();
if (response.isLeft()) {
expect(response.value.kind).toEqual(
"IResponseErrorForbiddenNotAuthorizedForProduction"
);
}
}
)
);
});

it("should respond with ResponseErrorForbiddenNotAuthorizedForRecipient when service is trying to send message to an unauthorized recipient", () => {
fc.assert(
fc.property(
fiscalCodeArrayArb,
fiscalCodeArb,
(authorizedRecipients, recipient) => {
const response = canWriteMessage(
new Set([UserGroup.ApiLimitedMessageWrite]),
new Set(authorizedRecipients.filter(_ => _ !== recipient)), // current recipient is not authorized
recipient
);
expect(response.isLeft()).toBeTruthy();
if (response.isLeft()) {
expect(response.value.kind).toEqual(
"IResponseErrorForbiddenNotAuthorizedForRecipient"
);
}
}
)
);
});

it("should pass when service is trying to send message to an authorized recipient", () => {
fc.assert(
fc.property(
fiscalCodeArrayArb,
fiscalCodeArb,
(authorizedRecipients, recipient) => {
const response = canWriteMessage(
new Set([UserGroup.ApiLimitedMessageWrite]),
new Set([...authorizedRecipients, recipient]), // current recipient always authorized
recipient
);
expect(response.isRight()).toBeTruthy();
}
)
);
});

it("should pass when service can send messages to any recipient", () => {
fc.assert(
fc.property(
fiscalCodeSetArb,
fiscalCodeArb,
(authorizedRecipients, recipient) => {
const response = canWriteMessage(
new Set([UserGroup.ApiMessageWrite]),
authorizedRecipients,
recipient
);
expect(response.isRight()).toBeTruthy();
}
)
);
});
});

describe("canDefaultAddresses", () => {
it("should always respond with ResponseErrorForbiddenNotAuthorizedForDefaultAddresses when default addresses are provided", () => {
fc.assert(
fc.property(newMessageWithDefaultEmailArb, m => {
const response = canDefaultAddresses(m);
expect(response.isLeft()).toBeTruthy();
})
);
});
});

describe("canPaymentAmount", () => {
it("should authorize payment if under the allowed amount", () => {
fc.assert(
fc.property(
newMessageWithPaymentDataArb,
maxAmountArb,
(message, maxAmount) => {
const response = canPaymentAmount(message.content, maxAmount);
if (message.content.payment_data.amount <= maxAmount) {
expect(response.isRight()).toBeTruthy();
} else {
expect(response.isLeft()).toBeTruthy();
}
}
)
);
});
});

describe("createMessageDocument", () => {
const alphanumStringArb = fc
.array(upperCaseAlphaArb, 16, 16)
.map(_ => _.join("") as NonEmptyString);
const messageIdArb = alphanumStringArb;
const senderUserIdArb = alphanumStringArb;
const serviceIdArb = alphanumStringArb;
const ttlArb = fc
.integer(3600, 604800)
// tslint:disable-next-line:no-useless-cast
.map(_ => _ as number & WithinRangeInteger<3600, 604800>);

it("should create a Message document", async () => {
await fc.assert(
fc.asyncProperty(
messageIdArb,
senderUserIdArb,
fiscalCodeArb,
ttlArb,
serviceIdArb,
async (messageId, senderUserId, fiscalCode, ttl, senderServiceId) => {
const mockMessageModel = ({
create: jest.fn(() => Promise.resolve(right({})))
} as unknown) as MessageModel;
const responseTask = createMessageDocument(
messageId,
mockMessageModel,
senderUserId,
fiscalCode,
ttl,
senderServiceId
);

const response = await responseTask.run();

expect(mockMessageModel.create).toHaveBeenCalledTimes(1);
expect(response.isRight()).toBeTruthy();
expect(response.getOrElse(undefined)).toMatchObject({
fiscalCode,
id: messageId,
indexedId: messageId,
isPending: true,
kind: "INewMessageWithoutContent",
senderServiceId,
senderUserId,
timeToLiveSeconds: ttl
});
}
)
);
});
});
25 changes: 15 additions & 10 deletions CreateMessage/handler.ts
Expand Up @@ -137,7 +137,7 @@ type CreateMessageHandlerResponse = PromiseType<
/**
* Checks whether the client service can create a new message for the recipient
*/
const canWriteMessage = (
export const canWriteMessage = (
authGroups: IAzureApiAuthorization["groups"],
authorizedRecipients: IAzureUserAttributes["service"]["authorizedRecipients"],
fiscalCode: FiscalCode
Expand Down Expand Up @@ -168,8 +168,8 @@ const canWriteMessage = (
* Note that this feature is deprecated and the handler will always respond with
* a Forbidden response if default addresses are provided.
*/
const canDefaultAddresses = (
messagePayload: ApiNewMessageWithDefaults
export const canDefaultAddresses = (
messagePayload: ApiNewMessage
): Either<IResponseErrorForbiddenNotAuthorizedForDefaultAddresses, true> => {
// check whether the user is authorized to provide default addresses
if (messagePayload.default_addresses) {
Expand All @@ -184,23 +184,23 @@ const canDefaultAddresses = (
* Checks whether the client service is allowed to request a payment to the
* user and whether the amount is below the allowed limit.
*/
const canPaymentAmount = (
messageContent: ApiNewMessageWithDefaults["content"],
service: IAzureUserAttributes["service"]
export const canPaymentAmount = (
messageContent: ApiNewMessage["content"],
maxAllowedPaymentAmount: IAzureUserAttributes["service"]["maxAllowedPaymentAmount"]
): Either<IResponseErrorValidation, true> => {
const requestedAmount = messageContent.payment_data
? messageContent.payment_data.amount
: undefined;

const hasExceededAmount =
requestedAmount && requestedAmount > service.maxAllowedPaymentAmount;
requestedAmount && requestedAmount > maxAllowedPaymentAmount;

// check if the service wants to charge a valid amount to the user
if (hasExceededAmount) {
return left(
ResponseErrorValidation(
"Error while sending payment metadata",
`The requested amount (${requestedAmount} cents) exceeds the maximum allowed for this service (${service.maxAllowedPaymentAmount} cents)`
`The requested amount (${requestedAmount} cents) exceeds the maximum allowed for this service (${maxAllowedPaymentAmount} cents)`
)
);
}
Expand All @@ -213,7 +213,7 @@ const canPaymentAmount = (
* Note that this function only creates the metadata document, the content of
* the message is stored in a blob by an async activity.
*/
const createMessageDocument = (
export const createMessageDocument = (
messageId: NonEmptyString,
messageModel: MessageModel,
senderUserId: IAzureApiAuthorization["userId"],
Expand Down Expand Up @@ -420,7 +420,12 @@ export function CreateMessageHandler(
)
.chainSecond(
// check whether the client can ask for payment
fromEither(canPaymentAmount(messagePayload.content, service))
fromEither(
canPaymentAmount(
messagePayload.content,
service.maxAllowedPaymentAmount
)
)
)
.chainSecond(
// create a Message document in the database
Expand Down
4 changes: 4 additions & 0 deletions jest.config.js
@@ -0,0 +1,4 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
21 changes: 13 additions & 8 deletions package.json
Expand Up @@ -9,7 +9,8 @@
"prestart": "npm run build && func extensions install",
"start:host": "func start",
"start": "npm-run-all --parallel start:host watch",
"test": "echo \"No tests yet...\"",
"test": "jest",
"test:coverage": "jest --coverage",
"lint": "tslint -p ."
},
"description": "",
Expand All @@ -18,29 +19,33 @@
"@types/cors": "^2.8.4",
"@types/documentdb": "^1.10.5",
"@types/express": "^4.16.0",
"@types/nodemailer": "^4.6.2",
"@types/html-to-text": "^1.4.31",
"@types/jest": "^24.0.15",
"@types/nodemailer": "^4.6.2",
"azurite": "^3.1.2-preview",
"danger": "^4.0.2",
"danger-plugin-digitalcitizenship": "^0.3.1",
"fast-check": "^1.16.0",
"italia-tslint-rules": "^1.1.3",
"jest": "^24.8.0",
"nodemailer": "^4.6.7",
"npm-run-all": "^4.1.5",
"prettier": "^1.18.2",
"ts-jest": "^24.0.2",
"tslint": "^5.17.0",
"typescript": "^3.3.3",
"nodemailer": "^4.6.7",
"danger": "^4.0.2",
"danger-plugin-digitalcitizenship": "^0.3.1"
"typescript": "^3.3.3"
},
"dependencies": {
"cors": "^2.8.4",
"documentdb": "^1.12.2",
"durable-functions": "^1.2.2",
"express": "^4.15.3",
"fp-ts": "1.12.0",
"html-to-text": "^4.0.0",
"io-functions-commons": "^0.3.7",
"io-functions-express": "^0.1.0",
"io-ts": "1.8.5",
"italia-ts-commons": "^5.1.5",
"winston": "^3.2.1",
"html-to-text": "^4.0.0"
"winston": "^3.2.1"
}
}

0 comments on commit 70d9b61

Please sign in to comment.