From d83309f8bc3671fc14f2d3c49a90d218874f5dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Orzech?= Date: Fri, 20 Mar 2026 00:27:33 +0100 Subject: [PATCH] feat: add Pages API model, endpoints, and model tests Introduce GhostPage, PagesResponse, PageWrapper data classes for Ghost CMS static pages. Add CRUD endpoints (getPages, createPage, updatePage, deletePage) to GhostApiService. Include comprehensive unit tests for serialization and default values. --- .../microblog/data/api/GhostApiService.kt | 24 +++ .../swoosh/microblog/data/model/PageModels.kt | 26 +++ .../microblog/data/model/PageModelsTest.kt | 162 ++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 app/src/main/java/com/swoosh/microblog/data/model/PageModels.kt create mode 100644 app/src/test/java/com/swoosh/microblog/data/model/PageModelsTest.kt diff --git a/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt b/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt index cd41155..f3d240c 100644 --- a/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt +++ b/app/src/main/java/com/swoosh/microblog/data/api/GhostApiService.kt @@ -1,5 +1,7 @@ package com.swoosh.microblog.data.api +import com.swoosh.microblog.data.model.PageWrapper +import com.swoosh.microblog.data.model.PagesResponse import com.swoosh.microblog.data.model.PostWrapper import com.swoosh.microblog.data.model.PostsResponse import okhttp3.MultipartBody @@ -40,6 +42,28 @@ interface GhostApiService { @GET("ghost/api/admin/users/me/") suspend fun getCurrentUser(): Response + // --- Pages --- + + @GET("ghost/api/admin/pages/") + suspend fun getPages( + @Query("limit") limit: String = "all", + @Query("formats") formats: String = "html,plaintext,mobiledoc" + ): Response + + @POST("ghost/api/admin/pages/") + @Headers("Content-Type: application/json") + suspend fun createPage(@Body body: PageWrapper): Response + + @PUT("ghost/api/admin/pages/{id}/") + @Headers("Content-Type: application/json") + suspend fun updatePage( + @Path("id") id: String, + @Body body: PageWrapper + ): Response + + @DELETE("ghost/api/admin/pages/{id}/") + suspend fun deletePage(@Path("id") id: String): Response + @Multipart @POST("ghost/api/admin/images/upload/") suspend fun uploadImage( diff --git a/app/src/main/java/com/swoosh/microblog/data/model/PageModels.kt b/app/src/main/java/com/swoosh/microblog/data/model/PageModels.kt new file mode 100644 index 0000000..4524b67 --- /dev/null +++ b/app/src/main/java/com/swoosh/microblog/data/model/PageModels.kt @@ -0,0 +1,26 @@ +package com.swoosh.microblog.data.model + +data class PagesResponse( + val pages: List, + val meta: Meta? +) + +data class PageWrapper( + val pages: List +) + +data class GhostPage( + val id: String? = null, + val title: String? = null, + val slug: String? = null, + val url: String? = null, + val html: String? = null, + val plaintext: String? = null, + val mobiledoc: String? = null, + val status: String? = null, + val feature_image: String? = null, + val custom_excerpt: String? = null, + val created_at: String? = null, + val updated_at: String? = null, + val published_at: String? = null +) diff --git a/app/src/test/java/com/swoosh/microblog/data/model/PageModelsTest.kt b/app/src/test/java/com/swoosh/microblog/data/model/PageModelsTest.kt new file mode 100644 index 0000000..e3a704c --- /dev/null +++ b/app/src/test/java/com/swoosh/microblog/data/model/PageModelsTest.kt @@ -0,0 +1,162 @@ +package com.swoosh.microblog.data.model + +import com.google.gson.Gson +import org.junit.Assert.* +import org.junit.Test + +class PageModelsTest { + + private val gson = Gson() + + // --- GhostPage defaults --- + + @Test + fun `GhostPage all fields default to null`() { + val page = GhostPage() + assertNull(page.id) + assertNull(page.title) + assertNull(page.slug) + assertNull(page.url) + assertNull(page.html) + assertNull(page.plaintext) + assertNull(page.mobiledoc) + assertNull(page.status) + assertNull(page.feature_image) + assertNull(page.custom_excerpt) + assertNull(page.created_at) + assertNull(page.updated_at) + assertNull(page.published_at) + } + + @Test + fun `GhostPage stores all fields correctly`() { + val page = GhostPage( + id = "page-1", + title = "About", + slug = "about", + url = "https://blog.example.com/about/", + html = "

About us

", + plaintext = "About us", + mobiledoc = """{"version":"0.3.1"}""", + status = "published", + feature_image = "https://blog.example.com/img.jpg", + custom_excerpt = "Learn more about us", + created_at = "2024-01-01T00:00:00.000Z", + updated_at = "2024-06-15T12:00:00.000Z", + published_at = "2024-01-02T00:00:00.000Z" + ) + assertEquals("page-1", page.id) + assertEquals("About", page.title) + assertEquals("about", page.slug) + assertEquals("https://blog.example.com/about/", page.url) + assertEquals("

About us

", page.html) + assertEquals("About us", page.plaintext) + assertEquals("published", page.status) + assertEquals("https://blog.example.com/img.jpg", page.feature_image) + assertEquals("Learn more about us", page.custom_excerpt) + assertEquals("2024-01-01T00:00:00.000Z", page.created_at) + assertEquals("2024-06-15T12:00:00.000Z", page.updated_at) + assertEquals("2024-01-02T00:00:00.000Z", page.published_at) + } + + // --- GSON serialization --- + + @Test + fun `GhostPage serializes to JSON correctly`() { + val page = GhostPage( + id = "abc123", + title = "Contact", + slug = "contact", + status = "published" + ) + val json = gson.toJson(page) + assertTrue(json.contains("\"id\":\"abc123\"")) + assertTrue(json.contains("\"title\":\"Contact\"")) + assertTrue(json.contains("\"slug\":\"contact\"")) + assertTrue(json.contains("\"status\":\"published\"")) + } + + @Test + fun `GhostPage deserializes from JSON correctly`() { + val json = """{"id":"xyz","title":"FAQ","slug":"faq","status":"draft"}""" + val page = gson.fromJson(json, GhostPage::class.java) + assertEquals("xyz", page.id) + assertEquals("FAQ", page.title) + assertEquals("faq", page.slug) + assertEquals("draft", page.status) + } + + @Test + fun `GhostPage deserializes with missing optional fields`() { + val json = """{"id":"test"}""" + val page = gson.fromJson(json, GhostPage::class.java) + assertEquals("test", page.id) + assertNull(page.title) + assertNull(page.slug) + assertNull(page.html) + assertNull(page.status) + } + + // --- PagesResponse --- + + @Test + fun `PagesResponse deserializes with pages and pagination`() { + val json = """{ + "pages": [{"id": "1", "title": "About"}, {"id": "2", "title": "Contact"}], + "meta": {"pagination": {"page": 1, "limit": 15, "pages": 1, "total": 2, "next": null, "prev": null}} + }""" + val response = gson.fromJson(json, PagesResponse::class.java) + assertEquals(2, response.pages.size) + assertEquals("1", response.pages[0].id) + assertEquals("About", response.pages[0].title) + assertEquals("2", response.pages[1].id) + assertEquals("Contact", response.pages[1].title) + assertEquals(1, response.meta?.pagination?.page) + assertEquals(2, response.meta?.pagination?.total) + assertNull(response.meta?.pagination?.next) + } + + @Test + fun `PagesResponse deserializes with empty pages list`() { + val json = """{"pages": [], "meta": null}""" + val response = gson.fromJson(json, PagesResponse::class.java) + assertTrue(response.pages.isEmpty()) + assertNull(response.meta) + } + + // --- PageWrapper --- + + @Test + fun `PageWrapper wraps pages for API request`() { + val wrapper = PageWrapper(listOf(GhostPage(title = "New Page", status = "draft"))) + val json = gson.toJson(wrapper) + assertTrue(json.contains("\"pages\"")) + assertTrue(json.contains("\"New Page\"")) + } + + @Test + fun `PageWrapper serializes single page correctly`() { + val page = GhostPage( + title = "About Us", + slug = "about-us", + status = "published", + mobiledoc = """{"version":"0.3.1","atoms":[],"cards":[],"markups":[],"sections":[[1,"p",[[0,[],0,"Welcome"]]]]}""" + ) + val wrapper = PageWrapper(listOf(page)) + val json = gson.toJson(wrapper) + assertTrue(json.contains("\"title\":\"About Us\"")) + assertTrue(json.contains("\"slug\":\"about-us\"")) + assertTrue(json.contains("\"status\":\"published\"")) + } + + @Test + fun `GhostPage updated_at is preserved for PUT requests`() { + val page = GhostPage( + id = "page-1", + title = "Updated Title", + updated_at = "2024-06-15T12:00:00.000Z" + ) + val json = gson.toJson(page) + assertTrue(json.contains("\"updated_at\":\"2024-06-15T12:00:00.000Z\"")) + } +}