From fd21ee8df5d88ce555329be9c2a1a4effaea1245 Mon Sep 17 00:00:00 2001 From: Adam Matthiesen Date: Sun, 3 Mar 2024 07:06:58 -0800 Subject: [PATCH] Some basic tests --- package.json | 14 +- packages/astro-ghostcms/package.json | 6 + .../astro-ghostcms/src/api/ghostAPI.test.ts | 84 ++++++++++ .../astro-ghostcms/src/api/invariant.test.ts | 32 ++++ .../src/schemas/api/authors.test.ts | 146 ++++++++++++++++++ .../src/schemas/api/posts.test.ts | 55 +++++++ .../src/schemas/api/settings.test.ts | 36 +++++ .../src/schemas/userconfig.test.ts | 54 +++++++ packages/astro-ghostcms/vitest.config.ts | 12 ++ pnpm-lock.yaml | 9 +- 10 files changed, 440 insertions(+), 8 deletions(-) create mode 100644 packages/astro-ghostcms/src/api/ghostAPI.test.ts create mode 100644 packages/astro-ghostcms/src/api/invariant.test.ts create mode 100644 packages/astro-ghostcms/src/schemas/api/authors.test.ts create mode 100644 packages/astro-ghostcms/src/schemas/api/posts.test.ts create mode 100644 packages/astro-ghostcms/src/schemas/api/settings.test.ts create mode 100644 packages/astro-ghostcms/src/schemas/userconfig.test.ts create mode 100644 packages/astro-ghostcms/vitest.config.ts diff --git a/package.json b/package.json index 04ff3b53..1b518edb 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,14 @@ "lint:fix": "biome check --apply .", "ci:version": "pnpm changeset version", "ci:publish": "pnpm changeset publish", - "ci:test:api": "pnpm --filter astro-ghostcms test:ci", - "test:api": "pnpm --filter astro-ghostcms test", - "test:api:watch": "pnpm --filter astro-ghostcms test:watch", - "test:api:coverage": "pnpm --filter astro-ghostcms test:coverage", + "ci:test:integration": "pnpm --filter astro-ghostcms test:ci", + "test:integration": "pnpm --filter astro-ghostcms test", + "test:integration:watch": "pnpm --filter astro-ghostcms test:watch", + "test:integration:coverage": "pnpm --filter astro-ghostcms test:coverage", "test:create": "pnpm --filter create-astro-ghostcms test", - "test:slg": "pnpm --filter starlight-ghostcms test", - "test:slg:watch": "pnpm --filter starlight-ghostcms test:watch", - "test:slg:coverage": "pnpm --filter starlight-ghostcms test:coverage" + "test:starlight": "pnpm --filter starlight-ghostcms test", + "test:starlight:watch": "pnpm --filter starlight-ghostcms test:watch", + "test:starlight:coverage": "pnpm --filter starlight-ghostcms test:coverage" }, "devDependencies": { "@biomejs/biome": "1.5.3", diff --git a/packages/astro-ghostcms/package.json b/packages/astro-ghostcms/package.json index c81e3c24..64518bcb 100644 --- a/packages/astro-ghostcms/package.json +++ b/packages/astro-ghostcms/package.json @@ -49,11 +49,17 @@ "./open-graph/*": "./src/integrations/satoriog/routes/*" }, "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:ci": "vitest run --coverage.enabled --coverage.reporter='text-summary'" }, "peerDependencies": { "astro": "^4.4.1" }, "devDependencies": { + "vitest": "^1.3.0", + "vitest-fetch-mock": "^0.2.2" }, "dependencies": { "@astrojs/rss": "^4.0.5", diff --git a/packages/astro-ghostcms/src/api/ghostAPI.test.ts b/packages/astro-ghostcms/src/api/ghostAPI.test.ts new file mode 100644 index 00000000..c4599fc1 --- /dev/null +++ b/packages/astro-ghostcms/src/api/ghostAPI.test.ts @@ -0,0 +1,84 @@ +import { assert, beforeEach, describe, expect, test } from "vitest"; + +import { TSGhostContentAPI } from "@ts-ghost/content-api"; + +describe("content-api", () => { + let api: TSGhostContentAPI; + beforeEach(() => { + api = new TSGhostContentAPI("https://ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0"); + }); + + test("content-api", () => { + expect(api).toBeDefined(); + }); + + test("content-api shouldn't instantiate with an incorrect url", () => { + assert.throws(() => { + const api = new TSGhostContentAPI("ghost.org", "59d4bf56c73c04a18c867dc3ba", "v5.0"); + api.settings; + }); + }); + + test("content-api shouldn't instantiate with an incorrect key", () => { + assert.throws(() => { + const api = new TSGhostContentAPI("https://ghost.org", "a", "v5.0"); + api.settings; + }); + }); + + test("content-api shouldn't instantiate with an incorrect version", () => { + assert.throws(() => { + const api = new TSGhostContentAPI( + "https://ghost.org", + "1efedd9db174adee2d23d982:4b74dca0219bad629852191af326a45037346c2231240e0f7aec1f9371cc14e8", + // @ts-expect-error + "v4.0", + ); + api.settings; + }); + }); + + test("content-api.posts", () => { + expect(api.posts).toBeDefined(); + expect(api.posts.browse).toBeDefined(); + expect(api.posts.read).toBeDefined(); + }); + + test("content-api.pages", () => { + expect(api.pages).toBeDefined(); + expect(api.pages.browse).toBeDefined(); + expect(api.pages.read).toBeDefined(); + }); + + test("content-api.tags", () => { + expect(api.tags).toBeDefined(); + expect(api.tags.browse).toBeDefined(); + expect(api.tags.read).toBeDefined(); + }); + + test("content-api.tiers", () => { + expect(api.tiers).toBeDefined(); + expect(api.tiers.browse).toBeDefined(); + expect(api.tiers.read).toBeDefined(); + }); + + test("content-api.authors", () => { + expect(api.authors).toBeDefined(); + expect(api.authors.browse).toBeDefined(); + expect(api.authors.read).toBeDefined(); + // @ts-expect-error + expect(api.authors.add).toBeUndefined(); + // @ts-expect-error + expect(api.authors.edit).toBeUndefined(); + expect(api.authors).toBeDefined(); + }); + + test("content-api.settings", () => { + expect(api.settings).toBeDefined(); + expect(api.settings.fetch).toBeDefined(); + // @ts-expect-error + expect(api.settings.read).toBeUndefined(); + // @ts-expect-error + expect(api.settings.browse).toBeUndefined(); + }); +}); diff --git a/packages/astro-ghostcms/src/api/invariant.test.ts b/packages/astro-ghostcms/src/api/invariant.test.ts new file mode 100644 index 00000000..20c887b9 --- /dev/null +++ b/packages/astro-ghostcms/src/api/invariant.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; + +// Modified version of invariant script to allow tests +const isProduction = false; +const prefix: string = "Invariant failed"; +function invariant(condition: any, message?: string | (() => string)) { + if (condition) { + return; + } + if (isProduction) { + throw new Error(prefix); + } + const provided: string | undefined = + typeof message === "function" ? message() : message; + const value: string = provided ? `${prefix}: ${provided}` : prefix; + return value; +} + +// TEST SECTION +const testTrue = true; +const testFalse = false; +describe("test invariant", () => { + it("Test `true` value", () => { + invariant(testTrue, "This should not error"); + expect(null); + }); + + it("Test `false` value", () => { + invariant(testFalse, "This should Error"); + expect(String("Invariant failed")); + }); +}); diff --git a/packages/astro-ghostcms/src/schemas/api/authors.test.ts b/packages/astro-ghostcms/src/schemas/api/authors.test.ts new file mode 100644 index 00000000..9cee527b --- /dev/null +++ b/packages/astro-ghostcms/src/schemas/api/authors.test.ts @@ -0,0 +1,146 @@ +import createFetchMock from "vitest-fetch-mock"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; + +import { TSGhostContentAPI } from "@ts-ghost/content-api"; + +const fetchMocker = createFetchMock(vi); + +describe("authors api .browse() Args Type-safety", () => { + const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com"; + const key = process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba"; + const api = new TSGhostContentAPI(url, key, "v5.0"); + test(".browse() params shouldnt accept invalid params", () => { + // @ts-expect-error - shouldnt accept invalid params + const browse = api.authors.browse({ pp: 2 }); + expect(browse.getParams().browseParams).toStrictEqual({}); + }); + + test(".browse() 'order' params should ony accept fields values", () => { + // @ts-expect-error - order should ony contain field + expect(() => api.authors.browse({ order: "foo ASC" })).toThrow(); + // valid + expect(api.authors.browse({ order: "name ASC" }).getParams().browseParams).toStrictEqual({ + order: "name ASC", + }); + expect(api.authors.browse({ order: "name ASC,slug DESC" }).getParams().browseParams).toStrictEqual({ + order: "name ASC,slug DESC", + }); + expect( + api.authors.browse({ order: "name ASC,slug DESC,location ASC" }).getParams().browseParams + ).toStrictEqual({ + order: "name ASC,slug DESC,location ASC", + }); + // @ts-expect-error - order should ony contain field (There is a typo in location) + expect(() => api.authors.browse({ order: "name ASC,slug DESC,locaton ASC" })).toThrow(); + }); + + test(".browse() 'filter' params should ony accept valid field", () => { + expect(() => + api.authors.browse({ + // @ts-expect-error - order should ony contain field + filter: "foo:bar", + }) + ).toThrow(); + expect( + api.authors + .browse({ + filter: "name:bar", + }) + .getParams().browseParams + ).toStrictEqual({ + filter: "name:bar", + }); + expect( + api.authors + .browse({ + filter: "name:bar+slug:-test", + }) + .getParams().browseParams + ).toStrictEqual({ + filter: "name:bar+slug:-test", + }); + }); + + test(".browse 'fields' argument should ony accept valid fields", () => { + expect( + api.authors + .browse() + .fields({ + // @ts-expect-error - order should ony contain field + foo: true, + }) + .getOutputFields() + ).toEqual([]); + + expect(api.authors.browse().fields({ location: true }).getOutputFields()).toEqual(["location"]); + expect(api.authors.browse().fields({ name: true, website: true }).getOutputFields()).toEqual([ + "name", + "website", + ]); + }); +}); + +describe("authors resource mocked", () => { + let api: TSGhostContentAPI; + + beforeEach(() => { + api = new TSGhostContentAPI("https://my-ghost-blog.com", "59d4bf56c73c04a18c867dc3ba", "v5.0"); + fetchMocker.enableMocks(); + }); + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("aouthors should be fetched correctly", async () => { + const authors = api.authors; + expect(authors).not.toBeUndefined(); + const browseQuery = authors + .browse({ + page: 2, + }) + .fields({ + name: true, + id: true, + }); + expect(browseQuery).not.toBeUndefined(); + expect(browseQuery.getOutputFields()).toStrictEqual(["name", "id"]); + + fetchMocker.doMockOnce( + JSON.stringify({ + authors: [ + { + name: "foo", + id: "eaoizdjoa1321123", + }, + ], + meta: { + pagination: { + page: 1, + limit: 15, + pages: 1, + total: 1, + next: null, + prev: null, + }, + }, + }) + ); + const result = await browseQuery.fetch(); + expect(fetchMocker).toHaveBeenCalledTimes(1); + expect(fetchMocker).toHaveBeenCalledWith( + "https://my-ghost-blog.com/ghost/api/content/authors/?page=2&fields=name%2Cid&key=59d4bf56c73c04a18c867dc3ba", + { + headers: { + "Content-Type": "application/json", + "Accept-Version": "v5.0", + }, + } + ); + expect(result).not.toBeUndefined(); + if (result.success) { + expect(result.data.length).toBe(1); + expect(result.data[0].name).toBe("foo"); + expect(result.data[0].id).toBe("eaoizdjoa1321123"); + } + }); +}); \ No newline at end of file diff --git a/packages/astro-ghostcms/src/schemas/api/posts.test.ts b/packages/astro-ghostcms/src/schemas/api/posts.test.ts new file mode 100644 index 00000000..f682720b --- /dev/null +++ b/packages/astro-ghostcms/src/schemas/api/posts.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, test } from "vitest"; + +import { TSGhostContentAPI } from "@ts-ghost/content-api"; +import type { Post } from "./index"; + +const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com"; +const key = process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba"; + +describe("posts api .browse() Args Type-safety", () => { + const api = new TSGhostContentAPI(url, key, "v5.0"); + test(".browse() params shouldnt accept invalid params", () => { + // @ts-expect-error - shouldnt accept invalid params + const browse = api.posts.browse({ pp: 2 }); + expect(browse.getParams().browseParams).toStrictEqual({}); + + const outputFields = { + slug: true, + title: true, + // @ts-expect-error - shouldnt accept invalid params + foo: true, + } satisfies { [k in keyof Post]?: true | undefined }; + + const test = api.posts + .browse() + // @ts-expect-error - shouldnt accept invalid params + .fields(outputFields); + expect(test.getOutputFields()).toEqual(["slug", "title"]); + + const fields = ["slug", "title", "foo"] as const; + const unknownOriginFields = fields.reduce((acc, k) => { + acc[k as keyof Post] = true; + return acc; + }, {} as { [k in keyof Post]?: true | undefined }); + const result = api.posts.browse().fields(unknownOriginFields); + expect(result.getOutputFields()).toEqual(["slug", "title"]); + }); + test(".browse() params, output fields declare const", () => { + const outputFields = { + slug: true, + title: true, + } satisfies { [k in keyof Post]?: true | undefined }; + + const test = api.posts.browse().fields(outputFields); + expect(test.getOutputFields()).toEqual(["slug", "title"]); + + // @ts-expect-error - shouldnt accept invalid params + expect(() => api.posts.browse({ filter: "slugg:test" })).toThrow(); + // @ts-expect-error - shouldnt accept invalid params + expect(() => api.posts.browse({ filter: "slug:test,foo:-[bar,baz]" })).toThrow(); + expect(api.posts.browse({ filter: "slug:test,tags:-[bar,baz]" })).toBeDefined(); + expect(api.posts.browse({ filter: "slug:test,tags:[bar,baz]" })).toBeDefined(); + // @ts-expect-error - shouldnt accept invalid params + expect(() => api.posts.browse({ filter: "slug:test,food:-[bar,baz]" })).toThrow(); + }); +}); \ No newline at end of file diff --git a/packages/astro-ghostcms/src/schemas/api/settings.test.ts b/packages/astro-ghostcms/src/schemas/api/settings.test.ts new file mode 100644 index 00000000..e5da99da --- /dev/null +++ b/packages/astro-ghostcms/src/schemas/api/settings.test.ts @@ -0,0 +1,36 @@ +import { beforeEach, describe, expect, test } from "vitest"; + +import { TSGhostContentAPI } from "@ts-ghost/content-api"; + +const url = process.env.VITE_GHOST_URL || "https://my-ghost-blog.com"; +const key = process.env.VITE_GHOST_CONTENT_API_KEY || "59d4bf56c73c04a18c867dc3ba"; + +describe("settings integration tests browse", () => { + let api: TSGhostContentAPI; + beforeEach(() => { + api = new TSGhostContentAPI(url, key, "v5.0"); + }); + test("settings.fetch()", async () => { + const result = await api.settings.fetch(); + expect(result).not.toBeUndefined(); + expect(result).not.toBeNull(); + if (!result.success) { + expect(result.errors).toBeDefined(); + expect(result.errors).toHaveLength(1); + } else { + expect(result.data).toBeDefined(); + const settings = result.data; + expect(settings).toBeDefined(); + expect(settings.title).toBe("Astro Starter"); + expect(settings.description).toBe("Thoughts, stories and ideas."); + expect(settings.logo).toBeNull(); + expect(settings.cover_image).toBe("https://static.ghost.org/v4.0.0/images/publication-cover.jpg"); + expect(settings.icon).toBeNull(); + expect(settings.lang).toBe("en"); + expect(settings.timezone).toBe("Etc/UTC"); + expect(settings.codeinjection_head).toBeNull(); + expect(settings.codeinjection_foot).toBeNull(); + expect(settings.members_support_address).toBe("noreply"); + } + }); +}); \ No newline at end of file diff --git a/packages/astro-ghostcms/src/schemas/userconfig.test.ts b/packages/astro-ghostcms/src/schemas/userconfig.test.ts new file mode 100644 index 00000000..8c1274bb --- /dev/null +++ b/packages/astro-ghostcms/src/schemas/userconfig.test.ts @@ -0,0 +1,54 @@ +import { expect, describe, it } from "vitest"; +import { GhostUserConfigSchema } from "./userconfig"; + +describe("GhostUserConfigSchema", () => { + it("should validate a valid user config", () => { + const validConfig = { + ghostURL: "https://ghostdemo.matthiesen.xyz", + disableThemeProvider: true, + ThemeProvider: { + theme: "@matthiesenxyz/astro-ghostcms-theme-default", + }, + disableDefault404: false, + enableRSSFeed: true, + enableOGImages: true, + sitemap: { + // sitemap configuration + }, + robotstxt: { + // robotstxt configuration + }, + fullConsoleLogs: false, + }; + + const result = GhostUserConfigSchema.safeParse(validConfig); + + expect(result.success).to.be.true; + expect(result.data).to.deep.equal(validConfig); + }); + + it("should invalidate an invalid user config", () => { + const invalidConfig = { + ghostURL: "invalid-url", + disableThemeProvider: "true", + ThemeProvider: { + theme: 123, + }, + disableDefault404: "false", + enableRSSFeed: "true", + enableOGImages: "true", + sitemap: { + // invalid sitemap configuration + }, + robotstxt: { + // invalid robotstxt configuration + }, + fullConsoleLogs: "false", + }; + + const result = GhostUserConfigSchema.safeParse(invalidConfig); + + expect(result.success).to.be.false; + expect(!result.success).to.exist; + }); +}); \ No newline at end of file diff --git a/packages/astro-ghostcms/vitest.config.ts b/packages/astro-ghostcms/vitest.config.ts new file mode 100644 index 00000000..f19e0d02 --- /dev/null +++ b/packages/astro-ghostcms/vitest.config.ts @@ -0,0 +1,12 @@ +/// +/// +import { defineProject } from 'vitest/config' + +export default defineProject({ + test: { + globals: true, + include: ["./**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], + watchExclude: [".*\\/node_modules\\/.*", ".*\\/build\\/.*"], + exclude: ["node_modules", "dist", ".idea", ".git", ".cache"], + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4965dfb2..3546f039 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,13 @@ importers: vite: specifier: ^5.1.4 version: 5.1.4(@types/node@20.11.19) + devDependencies: + vitest: + specifier: ^1.3.0 + version: 1.3.0(@types/node@20.11.19)(@vitest/ui@1.3.0) + vitest-fetch-mock: + specifier: ^0.2.2 + version: 0.2.2(vitest@1.3.0) packages/astro-ghostcms-brutalbyelian: dependencies: @@ -8809,7 +8816,7 @@ packages: strip-literal: 2.0.0 tinybench: 2.6.0 tinypool: 0.8.2 - vite: 5.1.3(@types/node@20.11.19) + vite: 5.1.4(@types/node@20.11.19) vite-node: 1.3.0(@types/node@20.11.19) why-is-node-running: 2.2.2 transitivePeerDependencies: