test: cover repository create validation guards

This commit is contained in:
2026-03-16 13:51:03 -04:00
parent 7b1c51eae0
commit 85659f070b
2 changed files with 43 additions and 1 deletions

View File

@@ -113,7 +113,7 @@ Kanbn4Droid is an unofficial app to connect to and manipulate data stored in sel
- Tapping on the filter by tag or search buttonswhen either of them is applied disables the active filter. - Tapping on the filter by tag or search buttonswhen either of them is applied disables the active filter.
- When a card(s) is selected by a long press, the filter by tag and search buttons get hidden by the select all, move card and delete card buttons until all cards are deselected. - When a card(s) is selected by a long press, the filter by tag and search buttons get hidden by the select all, move card and delete card buttons until all cards are deselected.
- When a card(s) is selected by a long press, the back arrow in the title bar and the back system button remove all selections. - When a card(s) is selected by a long press, the back arrow in the title bar and the back system button remove all selections.
- Current status: implemented in `BoardDetailActivity` with `ViewPager2` (one list per page), inline list-title edit, card rendering (title/tags/due date locale formatting and expiry color), cross-page card selection, page-scoped select-all, move dialog with list selector, two-step delete confirmation, mutation guards while in progress, and API-backed reload/reconciliation behavior through `BoardDetailViewModel` and `BoardDetailRepository`. Card move requests try these variants in order for compatibility across Kan.bn API versions: `PUT /api/v1/cards/{cardPublicId}` with `listPublicId`, then a GET+full-body `PUT /api/v1/cards/{cardPublicId}` payload (`title`, `description`, `index`, `listPublicId`, `dueDate`), then `PUT /api/v1/cards/{cardPublicId}` with `listId`, then `PATCH /api/v1/cards/{cardPublicId}` with `listId`. Board detail parsing now prefers public ids (`publicId`/`public_id`) over internal `id` values so follow-up card/list mutations target the correct API identifiers. Label chip border colors are hydrated from the Kan.bn `Get a label by public ID` endpoint (`colourCode`) and cached in-memory by `BoardDetailRepository` so each label color is fetched only once per app process. The repository now also exposes create-list and create-card methods that validate/normalize user input and delegate to API create endpoints using public IDs. Selection action icons use local vector drawables (`ic_select_all_grid_24`, `ic_move_cards_horizontal_24`, `ic_delete_24`) with day/night resource variants so dark mode uses light icon fills automatically. Startup blocking dialogs are shown for missing board id and missing session. - Current status: implemented in `BoardDetailActivity` with `ViewPager2` (one list per page), inline list-title edit, card rendering (title/tags/due date locale formatting and expiry color), cross-page card selection, page-scoped select-all, move dialog with list selector, two-step delete confirmation, mutation guards while in progress, and API-backed reload/reconciliation behavior through `BoardDetailViewModel` and `BoardDetailRepository`. Card move requests try these variants in order for compatibility across Kan.bn API versions: `PUT /api/v1/cards/{cardPublicId}` with `listPublicId`, then a GET+full-body `PUT /api/v1/cards/{cardPublicId}` payload (`title`, `description`, `index`, `listPublicId`, `dueDate`), then `PUT /api/v1/cards/{cardPublicId}` with `listId`, then `PATCH /api/v1/cards/{cardPublicId}` with `listId`. Board detail parsing now prefers public ids (`publicId`/`public_id`) over internal `id` values so follow-up card/list mutations target the correct API identifiers. Label chip border colors are hydrated from the Kan.bn `Get a label by public ID` endpoint (`colourCode`) and cached in-memory by `BoardDetailRepository` so each label color is fetched only once per app process. Selection action icons use local vector drawables (`ic_select_all_grid_24`, `ic_move_cards_horizontal_24`, `ic_delete_24`) with day/night resource variants so dark mode uses light icon fills automatically. Startup blocking dialogs are shown for missing board id and missing session.
**Card detail view** **Card detail view**
- The view shows the card's title in bold letters. Tapping on the card's title allows editing it. - The view shows the card's title in bold letters. Tapping on the card's title allows editing it.

View File

@@ -275,6 +275,16 @@ class BoardDetailRepositoryTest {
assertEquals("List title is required", (result as BoardsApiResult.Failure).message) assertEquals("List title is required", (result as BoardsApiResult.Failure).message)
} }
@Test
fun createListRejectsBlankBoardPublicId() = runTest {
val repository = createRepository()
val result = repository.createList(boardPublicId = " ", title = "New List")
assertTrue(result is BoardsApiResult.Failure)
assertEquals("Board id is required", (result as BoardsApiResult.Failure).message)
}
@Test @Test
fun createListDelegatesToApiWithTrimmedIds() = runTest { fun createListDelegatesToApiWithTrimmedIds() = runTest {
val apiClient = FakeBoardDetailApiClient() val apiClient = FakeBoardDetailApiClient()
@@ -288,6 +298,38 @@ class BoardDetailRepositoryTest {
assertEquals(0, apiClient.lastCreateListAppendIndex) assertEquals(0, apiClient.lastCreateListAppendIndex)
} }
@Test
fun createCardRejectsBlankListPublicId() = runTest {
val repository = createRepository()
val result = repository.createCard(
listPublicId = " ",
title = "Card",
description = null,
dueDate = null,
tagPublicIds = emptyList(),
)
assertTrue(result is BoardsApiResult.Failure)
assertEquals("List id is required", (result as BoardsApiResult.Failure).message)
}
@Test
fun createCardRejectsBlankTitle() = runTest {
val repository = createRepository()
val result = repository.createCard(
listPublicId = "list-1",
title = " ",
description = null,
dueDate = null,
tagPublicIds = emptyList(),
)
assertTrue(result is BoardsApiResult.Failure)
assertEquals("Card title is required", (result as BoardsApiResult.Failure).message)
}
@Test @Test
fun getBoardDetailValidatesBoardId() = runTest { fun getBoardDetailValidatesBoardId() = runTest {
val repository = createRepository() val repository = createRepository()