Skip to content

Commit

Permalink
Improve test coverage for encode.ts file (#47)
Browse files Browse the repository at this point in the history
* Add test for headerBysquare
* Add test for headerBysquare version
* Add test for headerBysquare document type and reserved nibble
* Add test for direct debit.
* Add test for removeDiacritics
  • Loading branch information
lukasbicus authored Oct 8, 2024
1 parent 447f939 commit e7cc405
Show file tree
Hide file tree
Showing 2 changed files with 259 additions and 25 deletions.
230 changes: 212 additions & 18 deletions src/encode.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import assert from "node:assert";
import test from "node:test";
import test, { describe } from "node:test";

import { decode } from "./decode.js";
import {
addChecksum,
encode,
EncodeError,
EncodeErrorMessage,
headerBysquare,
headerDataLength,
MAX_COMPRESSED_SIZE,
removeDiacritics,
serialize,
} from "./encode.js";
import {
CurrencyCode,
DataModel,
PaymentOptions,
Version,
} from "./types.js";

export const payload = {
Expand Down Expand Up @@ -51,15 +57,113 @@ const serialized = /** dprint-ignore */ [
"\t",
].join("");

const payloadWithStandingOrder = {
invoiceId: "random-id",
payments: [
{
type: PaymentOptions.StandingOrder,
amount: 100.0,
bankAccounts: [
{ iban: "SK9611000000002918599669" },
],
currencyCode: CurrencyCode.EUR,
variableSymbol: "123",
},
],
} satisfies DataModel;

const serializedStandingOrder = /** dprint-ignore */ [
"random-id",
"\t", "1",
"\t", "2",
"\t", "100",
"\t", "EUR",
"\t",
"\t", "123",
"\t",
"\t",
"\t",
"\t",
"\t", "1",
"\t", "SK9611000000002918599669",
"\t",
"\t", "1",
"\t",
"\t",
"\t",
"\t",
"\t", "0",
"\t",
"\t",
"\t",
].join("");

const payloadWithDirectDebit = {
invoiceId: "random-id",
payments: [
{
type: PaymentOptions.DirectDebit,
amount: 100.0,
bankAccounts: [
{ iban: "SK9611000000002918599669" },
],
currencyCode: CurrencyCode.EUR,
variableSymbol: "123",
},
],
} satisfies DataModel;

const serializedDirectDebit = /** dprint-ignore */ [
"random-id",
"\t", "1",
"\t", "4",
"\t", "100",
"\t", "EUR",
"\t",
"\t", "123",
"\t",
"\t",
"\t",
"\t",
"\t", "1",
"\t", "SK9611000000002918599669",
"\t",
"\t", "0",
"\t", "1",
"\t",
"\t",
"\t", "123",
"\t",
"\t",
"\t",
"\t",
"\t",
"\t",
"\t",
"\t",
"\t",
"\t",
].join("");

test("encode", () => {
const encoded = encode(payload);
const decoded = decode(encoded);
assert.deepStrictEqual(payload, decoded);
});

test("encode - serialize", () => {
const created = serialize(payload);
assert.equal(created, serialized);
describe("encode - serialize", () => {
test("serializes a payment order", () => {
const created = serialize(payload);
assert.equal(created, serialized);
});
test("serializes a standing order", () => {
const created = serialize(payloadWithStandingOrder);
assert.equal(created, serializedStandingOrder);
});
test("serializes a direct debit", () => {
const created = serialize(payloadWithDirectDebit);
assert.equal(created, serializedDirectDebit);
});
});

test("encode - create data with checksum", () => {
Expand All @@ -69,21 +173,111 @@ test("encode - create data with checksum", () => {
assert.deepEqual(checksum, expected);
});

test("encode - make bysquare header", () => {
const header = headerBysquare();
const expected = Uint8Array.from([0x00, 0x00]);
assert.deepEqual(header, expected);
});

test("encode - binary header", () => {
assert.deepEqual(
headerBysquare(/** dprint-ignore */ [
describe("encode - headerBysquare", () => {
test("make bysquare header", () => {
const header = headerBysquare();
const expected = Uint8Array.from([0x00, 0x00]);
assert.deepEqual(header, expected);
});
test("make bysquare header from binary data", () => {
assert.deepEqual(
headerBysquare(/** dprint-ignore */ [
0b0000_0001, 0b0000_0010,
0b0000_0011, 0b0000_0100,
]),
Uint8Array.from([
0b0001_0010,
0b0011_0100,
]),
);
Uint8Array.from([
0b0001_0010,
0b0011_0100,
]),
);
});
test("throw EncodeError when creating an bysquare header with invalid type", () => {
const invalidValue = 0x1F;
assert.throws(() => {
headerBysquare([invalidValue, Version["1.0.0"], 0x00, 0x00]);
}, new EncodeError(EncodeErrorMessage.BySquareType, { invalidValue }));
});
test("throw EncodeError when creating an bysquare header with invalid version", () => {
const invalidValue = 0xFF;
assert.throws(() => {
headerBysquare([0x00, invalidValue, 0x00, 0x00]);
}, new EncodeError(EncodeErrorMessage.Version, { invalidValue }));
});
test("throw EncodeError when creating an bysquare header with invalid document type", () => {
const invalidValue = 0xFF;
assert.throws(() => {
headerBysquare([0x00, 0x00, invalidValue, 0x00]);
}, new EncodeError(EncodeErrorMessage.DocumentType, { invalidValue }));
});
test("throw EncodeError when creating an bysquare header with invalid reserved nibble", () => {
const invalidValue = 0xFF;
assert.throws(() => {
headerBysquare([0x00, 0x00, 0x00, invalidValue]);
}, new EncodeError(EncodeErrorMessage.Reserved, { invalidValue }));
});
});

describe("encode - headerDataLength", () => {
test("return encoded header data length", () => {
const length = MAX_COMPRESSED_SIZE - 1;
const dataView = new DataView(new ArrayBuffer(2));
dataView.setUint16(0, length, true);
assert.deepEqual(
headerDataLength(length),
new Uint8Array(dataView.buffer),
);
});
test("throw EncodeError, when allowed size of header is exceeded", () => {
assert.throws(
() => {
headerDataLength(MAX_COMPRESSED_SIZE);
},
new EncodeError(EncodeErrorMessage.HeaderDataSize, {
actualSize: MAX_COMPRESSED_SIZE,
allowedSize: MAX_COMPRESSED_SIZE,
}),
);
});
});

describe("encode - removeDiacritics", function() {
const payloadWithDiacritics = {
invoiceId: "random-id",
payments: [
{
type: PaymentOptions.PaymentOrder,
amount: 100.0,
bankAccounts: [
{ iban: "SK9611000000002918599669" },
],
currencyCode: CurrencyCode.EUR,
variableSymbol: "123",
paymentNote: "Príspevok na kávu",
beneficiary: {
name: "Ján Kováč",
city: "Košice",
street: "Štúrova 27",
},
},
],
} satisfies DataModel;
test("Removes diacritics from payload", function() {
const input = Object.assign(
{},
JSON.parse(JSON.stringify(payloadWithDiacritics)),
) satisfies DataModel;
removeDiacritics(input);
assert.deepEqual(input, {
...payloadWithDiacritics,
payments: [{
...payloadWithDiacritics.payments[0],
paymentNote: "Prispevok na kavu",
beneficiary: {
name: "Jan Kovac",
city: "Kosice",
street: "Sturova 27",
},
}],
});
});
});
54 changes: 47 additions & 7 deletions src/encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,44 @@ import {
} from "./types.js";
import { validateDataModel } from "./validations.js";

const MAX_COMPRESSED_SIZE = 131_072; // 2^17
export enum EncodeErrorMessage {
/**
* @description - find invalid value in extensions
*/
BySquareType = `Invalid BySquareType value in header, valid range <0,15>`,
/**
* @description - find invalid value in extensions
* @see {@link ./types#Version} for valid ranges
*/
Version = `Invalid Version value in header`,
/**
* @description - find invalid value in extensions
*/
DocumentType = `Invalid DocumentType value in header, valid range <0,15>`,
/**
* @description - find invalid value in extensions
*/
Reserved = `Invalid Reserved value in header, valid range <0,15>`,
/**
* @description - find actual size of header in extensions
* @see MAX_COMPRESSED_SIZE
*/
HeaderDataSize = `Allowed header data size exceeded`,
}

export class EncodeError extends Error {
override name = "EncodeError";
public extensions?: { [name: string]: any; };

constructor(message: EncodeErrorMessage, extensions?: { [name: string]: any; }) {
super(message);
if (extensions) {
this.extensions = extensions;
}
}
}

export const MAX_COMPRESSED_SIZE = 131_072; // 2^17

/**
* Returns a 2 byte buffer that represents the header of the bysquare
Expand Down Expand Up @@ -38,16 +75,16 @@ export function headerBysquare(
],
): Uint8Array {
if (header[0] < 0 || header[0] > 15) {
throw new Error(`Invalid BySquareType value '${header[0]}' in header, valid range <0,15>`);
throw new EncodeError(EncodeErrorMessage.BySquareType, { invalidValue: header[0] });
}
if (header[1] < 0 || header[1] > 15) {
throw new Error(`Invalid Version value '${header[1]}' in header, valid range <0,15>`);
throw new EncodeError(EncodeErrorMessage.Version, { invalidValue: header[1] });
}
if (header[2] < 0 || header[2] > 15) {
throw new Error(`Invalid DocumentType value '${header[2]}' in header, valid range <0,15>`);
throw new EncodeError(EncodeErrorMessage.DocumentType, { invalidValue: header[2] });
}
if (header[3] < 0 || header[3] > 15) {
throw new Error(`Invalid Reserved value '${header[3]}' in header, valid range <0,15>`);
throw new EncodeError(EncodeErrorMessage.Reserved, { invalidValue: header[3] });
}

const [
Expand All @@ -72,7 +109,10 @@ export function headerBysquare(
*/
export function headerDataLength(length: number): Uint8Array {
if (length >= MAX_COMPRESSED_SIZE) {
throw new Error(`Data size ${length} exceeds limit of ${MAX_COMPRESSED_SIZE} bytes`);
throw new EncodeError(EncodeErrorMessage.HeaderDataSize, {
actualSize: length,
allowedSize: MAX_COMPRESSED_SIZE,
});
}

const header = new ArrayBuffer(2);
Expand Down Expand Up @@ -163,7 +203,7 @@ export function serialize(data: DataModel): string {
return serialized.join("\t");
}

function removeDiacritics(model: DataModel): void {
export function removeDiacritics(model: DataModel): void {
for (const payment of model.payments) {
if (payment.paymentNote) {
payment.paymentNote = deburr(payment.paymentNote);
Expand Down

0 comments on commit e7cc405

Please sign in to comment.