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.
This commit is contained in:
Paweł Orzech 2026-03-20 00:27:33 +01:00
parent 0891013df6
commit d83309f8bc
3 changed files with 212 additions and 0 deletions

View file

@ -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<UsersResponse>
// --- Pages ---
@GET("ghost/api/admin/pages/")
suspend fun getPages(
@Query("limit") limit: String = "all",
@Query("formats") formats: String = "html,plaintext,mobiledoc"
): Response<PagesResponse>
@POST("ghost/api/admin/pages/")
@Headers("Content-Type: application/json")
suspend fun createPage(@Body body: PageWrapper): Response<PagesResponse>
@PUT("ghost/api/admin/pages/{id}/")
@Headers("Content-Type: application/json")
suspend fun updatePage(
@Path("id") id: String,
@Body body: PageWrapper
): Response<PagesResponse>
@DELETE("ghost/api/admin/pages/{id}/")
suspend fun deletePage(@Path("id") id: String): Response<Unit>
@Multipart
@POST("ghost/api/admin/images/upload/")
suspend fun uploadImage(

View file

@ -0,0 +1,26 @@
package com.swoosh.microblog.data.model
data class PagesResponse(
val pages: List<GhostPage>,
val meta: Meta?
)
data class PageWrapper(
val pages: List<GhostPage>
)
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
)

View file

@ -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 = "<p>About us</p>",
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("<p>About us</p>", 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\""))
}
}