Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/src/createApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { healthRoutes } from "./routes/health.ts";
import postImagesRoutes from "./routes/tasks/post-images.ts";
import urlMetadataRoutes from "./routes/tasks/url-metadata.ts";
import collectionsRoutes from "./routes/content/collections.ts";
import postRoutes from "./routes/content/post.ts";
import fastify from "fastify";
import devRoutes from "./routes/dev/index.ts";

Expand All @@ -21,6 +22,7 @@ export const createApp = () => {
app.register(postImagesRoutes);
app.register(urlMetadataRoutes);
app.register(collectionsRoutes);
app.register(postRoutes);

if (env.ENVIRONMENT === "development") {
app.register(devRoutes);
Expand Down
288 changes: 288 additions & 0 deletions apps/api/src/routes/content/post.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import fastify, { type FastifyInstance } from "fastify";
import postRoutes from "./post.ts";
import { db } from "@playfulprogramming/db";

describe("Post Routes Tests", () => {
let app: FastifyInstance;
beforeAll(async () => {
app = fastify();
await app.register(postRoutes);
});

afterAll(async () => {
await app.close();
});

describe("/content/post/:slug", () => {
test("returns a post with its authors and a chapter list sorted by collectionOrder", async () => {
vi.mocked(db.query.posts.findFirst).mockResolvedValue({
slug: "chapter-two",
data: [
{
title: "Chapter Two",
description: "The second chapter",
bannerImage: "content/banner.png",
socialImage: "content/social.png",
wordCount: 500,
publishedAt: new Date("2024-01-15T00:00:00Z"),
},
],
authors: [
{
slug: "crutchcorn",
name: "Corbin Crutchley",
profileImage: "content/profile.png",
},
],
collection: {
slug: "example-collection",
posts: [
{
slug: "chapter-two",
collectionOrder: 1,
data: [{ title: "Chapter Two" }],
},
{
slug: "chapter-one",
collectionOrder: 0,
data: [{ title: "Chapter One" }],
},
],
},
} as never);

const response = await app.inject({
method: "GET",
url: "/content/post/chapter-two",
query: { locale: "en" },
});

expect(response.statusCode).toBe(200);
expect(response.json()).toMatchInlineSnapshot(`
{
"authors": [
{
"id": "crutchcorn",
"name": "Corbin Crutchley",
"profileImageUrl": "https://s3_public_url.test/s3_bucket/content/profile.png",
},
],
"bannerUrl": "https://s3_public_url.test/s3_bucket/content/banner.png",
"chapters": [
{
"collectionOrder": 0,
"isCurrent": false,
"slug": "chapter-one",
"title": "Chapter One",
},
{
"collectionOrder": 1,
"isCurrent": true,
"slug": "chapter-two",
"title": "Chapter Two",
},
],
"description": "The second chapter",
"publishedAt": "2024-01-15T00:00:00.000Z",
"slug": "chapter-two",
"socialImageUrl": "https://s3_public_url.test/s3_bucket/content/social.png",
"title": "Chapter Two",
"wordCount": 500,
}
`);
});

test("returns an empty chapter list when the post is not part of a collection", async () => {
vi.mocked(db.query.posts.findFirst).mockResolvedValue({
slug: "standalone-post",
data: [
{
title: "Standalone Post",
description: "A post with no collection",
bannerImage: null,
socialImage: null,
wordCount: 200,
publishedAt: new Date("2024-01-15T00:00:00Z"),
},
],
authors: [
{ slug: "crutchcorn", name: "Corbin Crutchley", profileImage: null },
],
collection: null,
} as never);

const response = await app.inject({
method: "GET",
url: "/content/post/standalone-post",
query: { locale: "en" },
});

expect(response.statusCode).toBe(200);
expect(response.json()).toMatchInlineSnapshot(`
{
"authors": [
{
"id": "crutchcorn",
"name": "Corbin Crutchley",
},
],
"chapters": [],
"description": "A post with no collection",
"publishedAt": "2024-01-15T00:00:00.000Z",
"slug": "standalone-post",
"title": "Standalone Post",
"wordCount": 200,
}
`);
});

test("returns 404 when the requested post is unpublished for the locale", async () => {
vi.mocked(db.query.posts.findFirst).mockResolvedValue({
slug: "draft-post",
data: [
{
title: "Draft Post",
description: "Not yet published",
bannerImage: null,
socialImage: null,
wordCount: 100,
publishedAt: null,
},
],
authors: [],
collection: null,
} as never);

const response = await app.inject({
method: "GET",
url: "/content/post/draft-post",
query: { locale: "en" },
});

expect(response.statusCode).toBe(404);
expect(response.json()).toMatchInlineSnapshot(`
{
"error": "Post not found",
}
`);
});

test("excludes unpublished sibling chapters from the chapter list", async () => {
vi.mocked(db.query.posts.findFirst).mockResolvedValue({
slug: "chapter-one",
data: [
{
title: "Chapter One",
description: "The first chapter",
bannerImage: null,
socialImage: null,
wordCount: 300,
publishedAt: new Date("2024-01-15T00:00:00Z"),
},
],
authors: [],
collection: {
slug: "example-collection",
posts: [
{
slug: "chapter-one",
collectionOrder: 0,
data: [
{
title: "Chapter One",
publishedAt: new Date("2024-01-15T00:00:00Z"),
},
],
},
{
slug: "chapter-two-draft",
collectionOrder: 1,
data: [{ title: "Chapter Two (Draft)", publishedAt: null }],
},
{
slug: "chapter-three",
collectionOrder: 2,
data: [
{
title: "Chapter Three",
publishedAt: new Date("2024-01-20T00:00:00Z"),
},
],
},
],
},
} as never);

const response = await app.inject({
method: "GET",
url: "/content/post/chapter-one",
query: { locale: "en" },
});

expect(response.statusCode).toBe(200);
expect(response.json()).toMatchInlineSnapshot(`
{
"authors": [],
"chapters": [
{
"collectionOrder": 0,
"isCurrent": true,
"slug": "chapter-one",
"title": "Chapter One",
},
{
"collectionOrder": 2,
"isCurrent": false,
"slug": "chapter-three",
"title": "Chapter Three",
},
],
"description": "The first chapter",
"publishedAt": "2024-01-15T00:00:00.000Z",
"slug": "chapter-one",
"title": "Chapter One",
"wordCount": 300,
}
`);
});

test("returns 404 when the post does not exist", async () => {
vi.mocked(db.query.posts.findFirst).mockResolvedValue(undefined);

const response = await app.inject({
method: "GET",
url: "/content/post/non-existent-post",
query: { locale: "en" },
});

expect(response.statusCode).toBe(404);
expect(response.json()).toMatchInlineSnapshot(`
{
"error": "Post not found",
}
`);
});

test("returns 404 when the post has no post_data row for the requested locale", async () => {
vi.mocked(db.query.posts.findFirst).mockResolvedValue({
slug: "spanish-only-post",
data: [],
authors: [],
collection: null,
} as never);

const response = await app.inject({
method: "GET",
url: "/content/post/spanish-only-post",
query: { locale: "en" },
});

expect(response.statusCode).toBe(404);
expect(response.json()).toMatchInlineSnapshot(`
{
"error": "Post not found",
}
`);
});
});
});
Loading