From 85659f070bf03aa357b56caf22c06c1c7af27f26 Mon Sep 17 00:00:00 2001 From: Wally Hackenslacker Date: Mon, 16 Mar 2026 13:51:03 -0400 Subject: [PATCH] test: cover repository create validation guards --- AGENTS.md | 2 +- .../boarddetail/BoardDetailRepositoryTest.kt | 42 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 7c74695..e2a985e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. - 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. -- 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** - The view shows the card's title in bold letters. Tapping on the card's title allows editing it. diff --git a/app/src/test/java/space/hackenslacker/kanbn4droid/app/boarddetail/BoardDetailRepositoryTest.kt b/app/src/test/java/space/hackenslacker/kanbn4droid/app/boarddetail/BoardDetailRepositoryTest.kt index 662e503..f80d720 100644 --- a/app/src/test/java/space/hackenslacker/kanbn4droid/app/boarddetail/BoardDetailRepositoryTest.kt +++ b/app/src/test/java/space/hackenslacker/kanbn4droid/app/boarddetail/BoardDetailRepositoryTest.kt @@ -275,6 +275,16 @@ class BoardDetailRepositoryTest { 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 fun createListDelegatesToApiWithTrimmedIds() = runTest { val apiClient = FakeBoardDetailApiClient() @@ -288,6 +298,38 @@ class BoardDetailRepositoryTest { 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 fun getBoardDetailValidatesBoardId() = runTest { val repository = createRepository()