diff --git a/CHANGELOG.md b/CHANGELOG.md index 550ae38..6e2fbab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,27 @@ +# [1.3.0](https://github.com/Print-one/print-one-js/compare/v1.2.1...v1.3.0) (2024-07-01) + + +### Bug Fixes + +* batch status filter ([2b0e209](https://github.com/Print-one/print-one-js/commit/2b0e20917d66ad21af372c5ed19f8f80da20ae71)) +* batch status filter ([2db77de](https://github.com/Print-one/print-one-js/commit/2db77de99b2e53f85e459790e82828b91edd58db)) +* import in CsvOrder.ts ([#19](https://github.com/Print-one/print-one-js/issues/19)) ([40986aa](https://github.com/Print-one/print-one-js/commit/40986aac4deb5ff5d9b87358f30c4a9138998a5e)) +* typescript error on install ([8fb29c4](https://github.com/Print-one/print-one-js/commit/8fb29c4f83831d12510e31475fb3a6fdc4ed352f)) + + +### Features + +* :sparkles: added endpoint to get a CsvOrder by it's ID ([74b0313](https://github.com/Print-one/print-one-js/commit/74b0313fefc98ddb45184c3b27a9615ea39fe7e7)) +* :sparkles: added remaining getBatches filters ([4c2996b](https://github.com/Print-one/print-one-js/commit/4c2996b4ad3cf29bab6daf32830ae419c9641e81)) +* :sparkles: allow for getting CSV order by id ([0560944](https://github.com/Print-one/print-one-js/commit/0560944128abdeea5cf0ff5ec79391201e0598a3)) +* :sparkles: allow for uploading CSVs to a Batch ([a4bd3a6](https://github.com/Print-one/print-one-js/commit/a4bd3a68ecc6f7d360ab751be88b22324249b6f6)) +* :sparkles: support batch order's API urls ([c2ca919](https://github.com/Print-one/print-one-js/commit/c2ca919f5d91f0618f2699d180e6bc8830d560b7)) +* :sparkles: webhook request validation logic ([ce81792](https://github.com/Print-one/print-one-js/commit/ce81792e9b554ceaf0694857a9402b003bb80392)) +* ✨ added CsvOrder model + endpoints ([#7](https://github.com/Print-one/print-one-js/issues/7)) ([9e04141](https://github.com/Print-one/print-one-js/commit/9e041416bdc09a3f34d6f2da72bde6788770e39b)) +* added format to DTO ([64ec3ad](https://github.com/Print-one/print-one-js/commit/64ec3adc8b51dd697d0c1244e663353b087a17c1)) +* batch endpoints added ([#8](https://github.com/Print-one/print-one-js/issues/8)) ([0099217](https://github.com/Print-one/print-one-js/commit/009921704b0c7b75206341ef20b1f540c31e366c)) +* implement PR feedback ([8420046](https://github.com/Print-one/print-one-js/commit/8420046c3655a0a9480f192f3879fcf9524c06f4)) + # [1.3.0-next.3](https://github.com/Print-one/print-one-js/compare/v1.3.0-next.2...v1.3.0-next.3) (2024-07-02) diff --git a/docs/Coupon.md b/docs/Coupon.md new file mode 100644 index 0000000..313efba --- /dev/null +++ b/docs/Coupon.md @@ -0,0 +1,96 @@ +Contains all information about a given CsvOrder + +# Fields + +| Name | Type | Description | +| ---------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------- | +| `id` | `string` | The ID of the coupon. | +| `name` | `string` | The name of the coupon. | +| `companyId` | `string` | The ID of the company the coupon belongs to. | +| `stats` | `object` | An object containing the stats of the coupon. With keys 'total', 'used' and 'remaining' | + +# Methods + +## `.refresh()` + +Refresh the Coupon data to get the latest information + +**Returns: `Promise`** + +**Example** + +```js +const coupon: Coupon; +await coupon.refresh(); +``` + +--- + +## `Coupon.getCodes()` + +Get all coupon codes within the coupon. | + +**Returns: [`Promise>`](./CouponCode)** + +**Example** + +```js +const couponCodes = await coupon.getCodes(); +``` + +--- + +## `Coupon.getCode(id)` + +Get all coupon codes by its ID. + +**Parameters** + +| Name | Type | Description | +| ---- | -------- | --------------------------------- | +| `id` | `string` | The ID of the coupon code to get. | + +**Returns: [`Promise>`](./CouponCode)** + +**Example** + +```js +const couponCode = await coupon.getCode('example-coupon-code-id'); +``` + +--- + +## `Coupon.addCodes(csv)` + +Add coupon codes to the coupon by uploading a CSV. + +**Parameters** + +| Name | Type | Description | +| ---------- | ------------- | --------------------------------- | +| `csv` | `ArrayBuffer` | The file to upload. Must be a CSV | + +**Returns: `Promise` + +**Example** + +```js +const data = fs.readFileSync("example.csv").buffer; +const file = await coupon.addCodes(data); +``` + + +--- + +## `Coupon.delete()` + +Delete the coupon. + +**Returns: `Promise` + +**Example** + +```js +await coupon.delete(); +``` + diff --git a/docs/CouponCode.md b/docs/CouponCode.md new file mode 100644 index 0000000..96af0ed --- /dev/null +++ b/docs/CouponCode.md @@ -0,0 +1,43 @@ +Contains all information about a given CsvOrder + +# Fields + +| Name | Type | Description | +| ---------------------- | ------------------------------------ | --------------------------------------------------------------------------------- | +| `id` | `string` | The ID of the coupon code. | +| `couponId` | `string` | The ID of the parent coupon. | +| `code` | `string` | The actual code saved for the coupon code`. | +| `used` | `boolean` | Whether the coupon code has been used. | +| `usedAt` | `Date` or `null` | The date at which the coupon code was used or `null` if not used yet. | +| `orderId` | `string` or `null` | The order ID by which the coupon code was used or `null` if not used yet. | + +# Methods +--- + +## `.refresh()` + +Refresh the CouponCode data to get the latest information + +**Returns: `Promise`** + +**Example** + +```js +const couponCode: CouponCOde; +await couponCode.refresh(); +``` + +--- + +## `.getOrder()` + +Get the order the coupon code was used by. You might need to do [`CouponCode.refresh`](#refresh) first when coupon was used after fetching. + +**Returns: [`Promise`](./Order)** + +**Example** + +```js +const couponCode: CouponCode +const order = await couponCode.getOrder();; +``` diff --git a/docs/PrintOne.md b/docs/PrintOne.md index fe323d9..06997bb 100644 --- a/docs/PrintOne.md +++ b/docs/PrintOne.md @@ -303,3 +303,77 @@ const csvOrder = await client.getCsvOrder("example-order-id"); ``` ``` + +-- + +## `.createCoupon(data)` + +Create a new coupon. + +**Parameters** + +| Name | Type | Description | +| ------ | -------- | ------------------------------------------------------------------------------------------- | +| `data` | `object` | The data to create the coupon with. See [`Order`](./Coupon#createcoupondata) for more info. | + +**Returns: [`Promise`](./Order)** + +**Example** + +```js +const coupon = await client.createCoupon({ + name: "coupon", +}); +``` + +--- + +## `.getCoupons([options])` + +Get all coupons. + +**Parameters** + +| Name | Type | Default | Description | +| ------------------------------- | ----------------------------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `options.limit` | `number` | `10` | The maximum number of coupons to return. | +| `options.page` | `number` | `1` | The page of coupons to return. | +| `options.sortBy` | [`sort`](./Filtering#Sorting) | `createdAt:DESC` | The field(s) to sort the coupons by. Can be `createdAt` or `name` | +| `options.filter.name` | `string` \| `string[]` | `undefined` | The name(s) of the coupon(s) to filter | + +**Returns: [`Promise>`](./Order)** + +**Example** + +```js +const coupons = await client.getCoupons({ + limit: 20, + page: 1, + sortBy: "createdAt:ASC", + filter: { + name: "test", + }, +}); +``` + +--- + +## `.getCoupon(id)` + +Get a coupon by its ID. + +**Parameters** + +| Name | Type | Description | +| ---- | -------- | ---------------------------- | +| `id` | `string` | The ID of the coupon to get. | + +**Returns: [`Promise`](./Coupon)** + +**Example** + +```js +const coupon = await client.getCoupon("example-coupon-id"); +``` + +--- \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a22c051..6c84d7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { "name": "@print-one/print-one-js", - "version": "1.3.0-next.3", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@print-one/print-one-js", - "version": "1.3.0-next.3", + "version": "1.3.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { "@jest/test-sequencer": "^29.7.0", diff --git a/package.json b/package.json index 45586d8..096d4ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@print-one/print-one-js", - "version": "1.3.0-next.3", + "version": "1.3.0", "description": "The official javascript client for Print.one", "license": "MIT", "author": "Print.one", diff --git a/src/PrintOne.ts b/src/PrintOne.ts index 0ff7421..61d7a2b 100644 --- a/src/PrintOne.ts +++ b/src/PrintOne.ts @@ -37,6 +37,8 @@ import { Webhook } from "~/models/Webhook"; import { CreateWebhook, IWebhook } from "~/models/_interfaces/IWebhook"; import { WebhookRequest, webhookRequestFactory } from "~/models/WebhookRequest"; import { IWebhookRequest } from "~/models/_interfaces/IWebhookRequest"; +import { Coupon, CreateCoupon } from "~/models/Coupon"; +import { ICoupon } from "~/models/_interfaces/ICoupon"; export type RequestHandler = new ( token: string, @@ -527,7 +529,62 @@ export class PrintOne { ); } - public isValidWebhook( + /** + * Create a coupon + * @param data The coupon data + */ + public async createCoupon(data: CreateCoupon): Promise { + const response = await this.client.POST("coupons", { + name: data.name, + }); + + return new Coupon(this.protected, response); + } + + /** + * Get all coupons. + * @param { PaginationOptions } options The options to use for pagination + * @param options.limit The maximum amount of coupons to return. + * @param options.page The page to return. + * @param options.sortBy The fields to sort by, can be "createdAt", "updatedAt". + * @param options.filter The filters to apply. + */ + public async getCoupons( + options: PaginationOptions<"name"> & { + filter?: { + name?: InFilter; + createdAt?: DateFilter; + }; + } = {}, + ): Promise> { + const params = { + ...sortToQuery(options), + ...inFilterToQuery("name", options.filter?.name), + }; + + const data = await this.client.GET>("coupons", { + params: params, + }); + + return PaginatedResponse.safe( + this.protected, + data, + (data) => new Coupon(this.protected, data), + ); + } + + /** + * Get a coupon by its id. + * @param { string } id The id of the coupon. + * @throws { PrintOneError } If the coupon could not be found. + */ + public async getCoupon(id: string): Promise { + const data = await this.client.GET(`coupons/${id}`); + + return new Coupon(this.protected, data); + } + + public validatedWebhook( body: string, headers: Record, secret: string, @@ -542,7 +599,7 @@ export class PrintOne { return hmac === hmacHeader; } - public validateWebhook( + public isValidWebhook( body: string, headers: Record, secret: string, diff --git a/src/index.ts b/src/index.ts index 8dce3f7..3b07253 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,10 @@ export * from "./PrintOne"; // Models export * from "./models/Address"; +export * from "./models/Batch"; export * from "./models/Company"; +export * from "./models/Coupon"; +export * from "./models/CouponCode"; export * from "./models/CsvOrder"; export * from "./models/CustomFile"; export * from "./models/Order"; @@ -14,7 +17,6 @@ export * from "./models/PaginatedResponse"; export * from "./models/Preview"; export * from "./models/PreviewDetails"; export * from "./models/Template"; -export * from "./models/Batch"; // Enums export * from "./enums/CsvStatus"; diff --git a/src/models/Coupon.ts b/src/models/Coupon.ts new file mode 100644 index 0000000..ea720f0 --- /dev/null +++ b/src/models/Coupon.ts @@ -0,0 +1,97 @@ +import { ICoupon } from "~/models/_interfaces/ICoupon"; +import { Protected } from "~/PrintOne"; +import { CouponCode } from "~/models/CouponCode"; +import { PaginatedResponse } from "~/models/PaginatedResponse"; +import { IPaginatedResponse } from "~/models/_interfaces/IPaginatedResponse"; +import { ICouponCode } from "~/models/_interfaces/ICouponCode"; + +export type CreateCoupon = { + name: string; +}; + +export class Coupon { + private _data: ICoupon; + + constructor( + private readonly _protected: Protected, + _data: ICoupon, + ) { + this._data = _data; + } + + public get id(): string { + return this._data.id; + } + + public get name(): string { + return this._data.name; + } + + public get companyId(): string { + return this._data.companyId; + } + + public get stats(): ICoupon["stats"] { + return this._data.stats; + } + + /** + * Refreshes the coupon data + * @throws { PrintOneError } If the coupon could not be refreshed. + */ + public async refresh(): Promise { + this._data = await this._protected.client.GET( + `/coupons/${this.id}`, + ); + } + + /** + * Get all coupon codes for the coupon + */ + public async getCodes(): Promise> { + const data = await this._protected.client.GET< + IPaginatedResponse + >(`/coupons/${this.id}/codes`); + + return PaginatedResponse.safe( + this._protected, + data, + (data) => new CouponCode(this._protected, data), + ); + } + + /** + * Get all coupon codes for the coupon + * @throws { PrintOneError } If the coupon code could not be found. + */ + public async getCode(codeId: string): Promise { + const data = await this._protected.client.GET( + `/coupons/${this.id}/codes/${codeId}`, + ); + return new CouponCode(this._protected, data); + } + + /** + * Add coupon codes to coupon with a CSV file + * The CSV file should have a header column and the first column should be the coupon codes + * @throws { PrintOneError } If the coupon codes could not be added. + */ + public async addCodes(csv: ArrayBuffer): Promise { + const formData = new FormData(); + formData.append("file", new Blob([csv], { type: "text/csv" }), "codes.csv"); + + await this._protected.client.POST(`/coupons/${this.id}`, formData, { + headers: { + "Content-Type": "multipart/form-data", + }, + }); + } + + /** + * Delete the coupon + * @throws { PrintOneError } If the coupon could not be deleted. + */ + public async delete(): Promise { + await this._protected.client.DELETE(`/coupons/${this.id}`); + } +} diff --git a/src/models/CouponCode.ts b/src/models/CouponCode.ts new file mode 100644 index 0000000..a487ac3 --- /dev/null +++ b/src/models/CouponCode.ts @@ -0,0 +1,62 @@ +import { Protected } from "~/PrintOne"; +import { ICouponCode } from "~/models/_interfaces/ICouponCode"; +import { Order } from "~/models/Order"; +import { IOrder } from "~/models/_interfaces/IOrder"; + +export class CouponCode { + private _data: ICouponCode; + + constructor( + private readonly _protected: Protected, + _data: ICouponCode, + ) { + this._data = _data; + } + + public get id(): string { + return this._data.id; + } + + public get couponId(): string { + return this._data.couponId; + } + + public get code(): string { + return this._data.code; + } + + public get used(): boolean { + return this._data.used; + } + + public get usedAt(): Date | null { + return this._data.usedAt ? new Date(this._data.usedAt) : null; + } + + public get orderId(): string | null { + return this._data.orderId; + } + + /** + * Refresh the coupon code + * @throws { PrintOneError } If the coupon code could not be refreshed. + */ + public async refresh(): Promise { + this._data = await this._protected.client.GET( + `/coupons/${this.couponId}/codes/${this.id}`, + ); + } + + /** + * Get the order the coupon was used on + * @throws { PrintOneError } If the order could not be fetched. + */ + public async getOrder(): Promise { + if (!this.orderId) return null; + + const data = await this._protected.client.GET( + `/orders/${this.orderId}`, + ); + return new Order(this._protected, data); + } +} diff --git a/src/models/_interfaces/ICoupon.ts b/src/models/_interfaces/ICoupon.ts new file mode 100644 index 0000000..5e104f4 --- /dev/null +++ b/src/models/_interfaces/ICoupon.ts @@ -0,0 +1,10 @@ +export type ICoupon = { + id: string; + name: string; + companyId: string; + stats: { + total: number; + used: number; + remaining: number; + }; +}; diff --git a/src/models/_interfaces/ICouponCode.ts b/src/models/_interfaces/ICouponCode.ts new file mode 100644 index 0000000..6efd509 --- /dev/null +++ b/src/models/_interfaces/ICouponCode.ts @@ -0,0 +1,8 @@ +export type ICouponCode = { + id: string; + couponId: string; + code: string; + used: boolean; + usedAt: null | Date; + orderId: null | string; +}; diff --git a/test/Coupon.spec.ts b/test/Coupon.spec.ts new file mode 100644 index 0000000..da1255b --- /dev/null +++ b/test/Coupon.spec.ts @@ -0,0 +1,121 @@ +import { Coupon, PaginatedResponse, PrintOneError } from "../src"; +import { client } from "./client"; +import * as fs from "fs"; +import * as path from "path"; + +let coupon: Coupon = null as unknown as Coupon; +let file: ArrayBuffer; + +beforeAll(function () { + file = fs.readFileSync(path.join(__dirname, "assets/codes.csv")); +}); + +beforeEach(async function () { + coupon = await client.createCoupon({ + name: `Test Coupon`, + }); +}); + +describe("refresh", function () { + it("should refresh the coupon", async function () { + // precondition + expect(coupon.stats.total).toEqual(0); + + // arrange + await coupon.addCodes(file); + + // act + await coupon.refresh(); + + // assert + expect(coupon.stats.total).toBe(25); + }, 30000); +}); + +describe("delete", function () { + it("should delete the order", async function () { + // arrange + + // act + await coupon.delete(); + + // assert + await expect(client.getCoupon(coupon.id)).rejects.toThrow(PrintOneError); + }, 30000); +}); + +describe("addCodes", function () { + it("should increase coupons total", async function () { + // arrange + await coupon.addCodes(file); + + // act + await coupon.refresh(); + + // assert + expect(coupon.stats.total).toBe(25); + }); +}); + +describe("getCodes", function () { + beforeEach(async function () { + await coupon.addCodes(file); + }); + + it("should return a paginated response", async function () { + // arrange + + // act + const codes = await coupon.getCodes(); + + // assert + expect(codes).toBeDefined(); + expect(codes).toEqual(expect.any(PaginatedResponse)); + + expect(codes.data).toBeDefined(); + expect(codes.data.length).toBeGreaterThanOrEqual(1); + + expect(codes.meta.total).toBeGreaterThanOrEqual(1); + expect(codes.meta.page).toEqual(1); + // Default page size is 10 + expect(codes.meta.pageSize).toBeGreaterThanOrEqual(10); + expect(codes.meta.pages).toBeGreaterThanOrEqual(1); + }); + + it("should return a coupon code with all fields", async function () { + // act + const coupons = await coupon.getCodes(); + const couponCode = coupons.data[0]; + + // assert + expect(couponCode.id).toEqual(expect.any(String)); + expect(couponCode.couponId).toEqual(expect.any(String)); + expect(couponCode.code).toEqual(expect.any(String)); + expect(couponCode.used).toEqual(expect.any(Boolean)); + expect(couponCode.usedAt).toEqual(null); + expect(couponCode.orderId).toEqual(null); + }); +}); + +describe("getCode", function () { + it("should get code by id", async function () { + //arrange + await coupon.addCodes(file); + const codes = await coupon.getCodes(); + const codeId = codes.data[0].id; + + //act + const code = await coupon.getCode(codeId); + + //assert + expect(code.couponId).toBe(coupon.id); + }); + + it("should throw error if code does not exist", async function () { + //act + const response = coupon.getCode("test"); + + //assert + await expect(response).rejects.toThrow(PrintOneError); + }); +}); diff --git a/test/CouponCode.spec.ts b/test/CouponCode.spec.ts new file mode 100644 index 0000000..ffffcb6 --- /dev/null +++ b/test/CouponCode.spec.ts @@ -0,0 +1,90 @@ +import { Coupon, CouponCode, Format, Order } from "../src"; +import { client } from "./client"; +import * as fs from "fs"; +import * as path from "path"; + +let coupon: Coupon = null as unknown as Coupon; +let couponCode: CouponCode = null as unknown as CouponCode; +let file: ArrayBuffer; + +beforeAll(function () { + file = fs.readFileSync(path.join(__dirname, "assets/single-code.csv")); +}); + +beforeEach(async function () { + coupon = await client.createCoupon({ + name: `Test Coupon`, + }); + + await coupon.addCodes(file); + + couponCode = (await coupon.getCodes()).data[0]; +}); + +const useCoupon = async function () { + const template = await client.createTemplate({ + name: `Test Order ${new Date().toISOString().replaceAll(":", "-")}`, + format: Format.POSTCARD_SQ15, + labels: ["library-unit-test"], + pages: ["{{get-coupon couponId}}", "page2"], + }); + + return await client.createOrder({ + recipient: { + name: "John Doe", + address: "123 Main Street", + postalCode: "1234 AB", + city: "Anytown", + country: "Nederland", + }, + template: template, + mergeVariables: { + couponId: coupon.id, + }, + }); +}; + +describe("refresh", function () { + it("should refresh the coupon code", async function () { + // precondition + expect(couponCode.used).toBe(false); + expect(couponCode.orderId).toBe(null); + expect(couponCode.usedAt).toBe(null); + + // arrange + const order = await useCoupon(); + + // act + await couponCode.refresh(); + + // assert + expect(couponCode.used).toBe(true); + expect(couponCode.orderId).toBe(order.id); + expect(couponCode.usedAt).toBeInstanceOf(Date); + }, 30000); +}); + +describe("getOrder", function () { + it("should return null if coupon code is not used yet", async function () { + // act + const order = await couponCode.getOrder(); + + // assert + expect(order).toBe(null); + }); + + it("should return order when coupon code is used", async function () { + // arrange + const preOrder = await useCoupon(); + const orderId = preOrder.id; + await couponCode.refresh(); + + // act + const order = await couponCode.getOrder(); + + // assert + expect(order).toBeDefined(); + expect(order).toBeInstanceOf(Order); + expect(order?.id).toBe(orderId); + }); +}); diff --git a/test/PrintOne.spec.ts b/test/PrintOne.spec.ts index b860909..603c14b 100644 --- a/test/PrintOne.spec.ts +++ b/test/PrintOne.spec.ts @@ -13,6 +13,7 @@ import { PaginatedResponse, PreviewDetails, Template, + Coupon, } from "../src"; import "jest-extended"; import * as fs from "fs"; @@ -1965,6 +1966,163 @@ describe("getBatches", function () { }); }); +describe("createCoupon", function () { + it("should create a coupon", async function () { + // arrange + + // act + const coupon = await client.createCoupon({ + name: "Test coupon", + }); + + // assert + expect(coupon).toBeDefined(); + expect(coupon).toEqual(expect.any(Coupon)); + }); + + it("should create a coupon with all fields", async function () { + // arrange + + // act + const coupon = await client.createCoupon({ + name: "Test coupon", + }); + + // assert + expect(coupon).toBeDefined(); + expect(coupon.id).toEqual(expect.any(String)); + expect(coupon.name).toEqual(expect.any(String)); + expect(coupon.companyId).toEqual(expect.any(String)); + expect(coupon.stats).toEqual({ + total: expect.any(Number), + used: expect.any(Number), + remaining: expect.any(Number), + }); + }); +}); + +describe("getCoupon", function () { + let couponId: string = null as unknown as string; + + // global arrange + beforeAll(async function () { + const coupon = await client.createCoupon({ + name: "Test coupon", + }); + couponId = coupon.id; + }); + + it("should return a coupon", async function () { + // arrange + + // act + const coupon = await client.getCoupon(couponId); + + // assert + expect(coupon).toBeDefined(); + expect(coupon).toEqual(expect.any(Coupon)); + }); + + it("should return a coupon with all fields", async function () { + // arrange + + // act + const coupon = await client.getCoupon(couponId); + + // assert + expect(coupon).toBeDefined(); + expect(coupon.id).toEqual(expect.any(String)); + expect(coupon.name).toEqual(expect.any(String)); + expect(coupon.companyId).toEqual(expect.any(String)); + expect(coupon.stats).toEqual({ + total: expect.any(Number), + used: expect.any(Number), + remaining: expect.any(Number), + }); + }); + + it("should throw an error when the coupon does not exist", async function () { + // arrange + + // act + const promise = client.getCoupon("test"); + + // assert + await expect(promise).rejects.toThrow(/not found/); + }); +}); + +describe("getCoupons", function () { + it("should return a paginated response", async function () { + // arrange + + // act + const coupons = await client.getCoupons(); + + // assert + expect(coupons).toBeDefined(); + expect(coupons).toEqual(expect.any(PaginatedResponse)); + + expect(coupons.data).toBeDefined(); + expect(coupons.data.length).toBeGreaterThanOrEqual(1); + + expect(coupons.meta.total).toBeGreaterThanOrEqual(1); + expect(coupons.meta.page).toEqual(1); + // Default page size is 10 + expect(coupons.meta.pageSize).toBeGreaterThanOrEqual(10); + expect(coupons.meta.pages).toBeGreaterThanOrEqual(1); + }); + + it("should return a coupon", async function () { + // arrange + + // act + const coupons = await client.getCoupons({ limit: 1 }); + const coupon = coupons.data[0]; + + // assert + expect(coupon).toBeDefined(); + expect(coupon).toEqual(expect.any(Coupon)); + }); + + it("should return a coupon with all fields", async function () { + // arrange + + // act + const coupons = await client.getCoupons({ limit: 1 }); + const coupon = coupons.data[0]; + + // assert + expect(coupon).toBeDefined(); + expect(coupon.id).toEqual(expect.any(String)); + expect(coupon.name).toEqual(expect.any(String)); + expect(coupon.companyId).toEqual(expect.any(String)); + expect(coupon.stats).toEqual({ + total: expect.any(Number), + used: expect.any(Number), + remaining: expect.any(Number), + }); + }); + + it("should apply the name filter", async function () { + // arrange + const couponName = (await client.getCoupons()).data[1]?.name ?? "test"; + + // act + const coupons = await client.getCoupons({ + limit: 1, + filter: { + name: couponName, + }, + }); + const coupon = coupons.data[0]; + + // assert + expect(coupon).toBeDefined(); + expect(coupon.name).toEqual(couponName); + }); +}); + describe("isValidWebhook", function () { const body = '{"data":{"id":"ord_QXitaPr7MumnHo2BYXuW9","companyId":"2bd4c679-3d59-4a6f-a815-a60424746f8d","templateId":"tmpl_AyDg3PxvP5ydyGq3kSFfj","finish":"GLOSSY","format":"POSTCARD_A5","mergeVariables":{},"recipient":{"name":"Your Name","address":"Street 1","postalCode":"1234 AB","city":"Amsterdam","country":"NL"},"definitiveCountryId":"NL","region":"NETHERLANDS","deliverySpeed":"FAST","isBillable":true,"status":"order_created","friendlyStatus":"Processing","errors":[],"metadata":{},"sendDate":"2024-01-01T00:00:00.000Z","createdAt":"2024-01-01T00:00:00.000Z","updatedAt":"2024-01-01T00:00:00.000Z","anonymizedAt":null,"csvOrderId":null},"created_at":"2024-06-03T13:14:46.501Z","event":"order_status_update"}'; diff --git a/test/assets/codes.csv b/test/assets/codes.csv new file mode 100644 index 0000000..35ff518 --- /dev/null +++ b/test/assets/codes.csv @@ -0,0 +1,26 @@ +codes +code_1 +code_2 +code_3 +code_4 +code_5 +code_6 +code_7 +code_8 +code_9 +code_10 +code_11 +code_12 +code_13 +code_14 +code_15 +code_16 +code_17 +code_18 +code_19 +code_20 +code_21 +code_22 +code_23 +code_24 +code_25 \ No newline at end of file diff --git a/test/assets/single-code.csv b/test/assets/single-code.csv new file mode 100644 index 0000000..b32d0f4 --- /dev/null +++ b/test/assets/single-code.csv @@ -0,0 +1,2 @@ +codes +happybday \ No newline at end of file