pelican/internal/board/status_lifecycle_test.go
Dan Anglin 68dcdae56d
All checks were successful
/ test (pull_request) Successful in 34s
/ lint (pull_request) Successful in 35s
added test cases for column repositioning
2024-01-24 01:09:56 +00:00

348 lines
12 KiB
Go

package board_test
import (
"errors"
"fmt"
"os"
"path/filepath"
"reflect"
"testing"
"codeflow.dananglin.me.uk/apollo/pelican/internal/board"
)
func TestStatusLifecycle(t *testing.T) {
t.Log("Testing the lifecycle of the statuses on the Kanban board...")
projectDir, err := projectRoot()
if err != nil {
t.Fatalf(err.Error())
}
testDBPath := filepath.Join(projectDir, "test", "databases", "Board_TestStatusLifecycle.db")
os.Remove(testDBPath)
kanban, err := board.Open(testDBPath)
if err != nil {
t.Fatalf("Unable to open the test Kanban board, %s.", err)
}
defer func() {
_ = kanban.Close()
}()
t.Logf("We've opened a new board with the default list of statuses: 'To Do', 'Doing' and 'Done'")
t.Logf("Let us now create a new status called 'On Hold' in the 4th position...")
statusOnHoldName := "On Hold"
statusOnHoldExpectedID := 4
statusOnHoldBoardPosition := 4
t.Run("Test Create Status (On Hold)", testCreateStatus(kanban, statusOnHoldName, statusOnHoldBoardPosition))
t.Run("Test Read Status (On Hold)", testReadStatus(kanban, statusOnHoldExpectedID, statusOnHoldName, statusOnHoldBoardPosition))
t.Logf("Additionally, let us create another status without specifying a position. It should take the last position on the board...")
statusNextName := "Next"
statusNextExpectedID := 5
statusNextExpectedBoardPosition := 5
t.Run("Test Create Status (Next)", testCreateStatus(kanban, statusNextName, 0))
t.Run("Test Read Status (Next)", testReadStatus(kanban, statusNextExpectedID, statusNextName, statusNextExpectedBoardPosition))
t.Logf("Let us see what happens when we try and receive a status that does not exist...")
t.Run("Test Read Non-Existent Status", testReadNonExistentStatus(kanban, 1000))
t.Logf("Let us now update the names of two of our statuses...")
t.Run("Test Status Update (Doing to In Progress)", testUpdateStatus(kanban, 2, 2, "In Progress"))
t.Run("Test Status Update (To Do to Backlog)", testUpdateStatus(kanban, 1, 1, "Backlog"))
t.Logf("Our current column positioning is: Backlog, In Progress, Done, On Hold, Next.")
t.Logf("Let us rearrange the board so that the order is: Backlog, Next, In Progress, On Hold, Done...")
// NOTE: the statuses current index is the index in the slice that is received from the database.
// The wantPositions is used to evaluate each statuses Position value after all the reshuffling and renormalisation.
rearrangeCases := []struct {
statusName string
currentIndex int
targetIndex int
wantPositions map[int]string
}{
{
statusName: "Done",
currentIndex: 2,
targetIndex: 4,
wantPositions: map[int]string{
1: "Backlog",
2: "In Progress",
3: "On Hold",
4: "Next",
5: "Done",
},
},
{
statusName: "Next",
currentIndex: 3,
targetIndex: 1,
wantPositions: map[int]string{
1: "Backlog",
2: "Next",
3: "In Progress",
4: "On Hold",
5: "Done",
},
},
}
for i := range rearrangeCases {
t.Run(
fmt.Sprintf("Test Re-Arrange Status: %s", rearrangeCases[i].statusName),
testRepositionStatuses(kanban, rearrangeCases[i].currentIndex, rearrangeCases[i].targetIndex, rearrangeCases[i].wantPositions),
)
}
t.Logf("Let us now try moving a card from one status to another...")
t.Run("Test Move Card To Status", testMoveCardToStatus(kanban))
expectedPositionsAfterDelete := map[int]string{
1: "Backlog",
2: "Next",
3: "In Progress",
4: "Done",
}
t.Logf("Let us now delete the 'On Hold' status from the database...")
t.Run("Test Delete Status (On Hold)", testDeleteEmptyStatus(kanban, statusOnHoldExpectedID, expectedPositionsAfterDelete))
t.Logf("Additionally, let us try to delete a status that contains a card...")
t.Run("Test Delete a non-empty status", testDeleteNonEmptyStatus(kanban, 2))
}
func testCreateStatus(kanban board.Board, name string, position int) func(t *testing.T) {
return func(t *testing.T) {
t.Log("When the status is created and saved to the database.")
args := board.StatusArgs{
Name: name,
Position: position,
}
if err := kanban.CreateStatus(args); err != nil {
t.Fatalf("ERROR: Unable to create the new status, %v.", err)
}
t.Logf("%s\tStatus '%s' was successfully saved to the database.", success, name)
}
}
func testReadStatus(kanban board.Board, statusID int, wantName string, wantPosition int) func(t *testing.T) {
return func(t *testing.T) {
t.Log("When the status is read from the database.")
status, err := kanban.Status(statusID)
if err != nil {
t.Fatalf("ERROR: Unable to read the test status, %v", err)
}
if status.Name != wantName {
t.Errorf("%s\tUnexpected status received from the database, want: '%s', got: '%s'", failure, wantName, status.Name)
} else {
t.Logf("%s\tSuccessfully received the '%s' status from the database.", success, status.Name)
}
if status.Position != wantPosition {
t.Errorf("%s\tUnexpected position found for %s, want: '%d', got: '%d'", failure, status.Name, wantPosition, status.Position)
} else {
t.Logf("%s\tSuccessfully received the expected position number for the '%s' status, got : '%d'.", success, status.Name, status.Position)
}
}
}
func testUpdateStatus(kanban board.Board, statusID, expectedPosition int, newName string) func(t *testing.T) {
return func(t *testing.T) {
t.Log("When the status' name is updated in the database.")
args := board.UpdateStatusArgs{
StatusID: statusID,
StatusArgs: board.StatusArgs{
Name: newName,
Position: -1,
},
}
if err := kanban.UpdateStatus(args); err != nil {
t.Fatalf("ERROR: Unable to update the status, %v", err)
} else {
t.Logf("%s\tStatus successfully updated.", success)
}
t.Log("\tVerifying the new status...")
status, err := kanban.Status(statusID)
if err != nil {
t.Fatalf("ERROR: Unable to retrieve the status from the database, %v", err)
}
want := board.Status{
Identity: board.Identity{ID: statusID},
Name: newName,
CardIds: nil,
Position: expectedPosition,
}
if !reflect.DeepEqual(status, want) {
t.Errorf("%s\tUnexpected status received from the database, want: %+v, got: %+v", failure, want, status)
} else {
t.Logf("%s\tExpected status name received from the database, got: %+v", success, status)
}
}
}
func testRepositionStatuses(kanban board.Board, currentIndex, targetIndex int, wantPositions map[int]string) func(t *testing.T) {
return func(t *testing.T) {
t.Logf("When repositioning a status on the board.")
if err := kanban.RepositionStatus(currentIndex, targetIndex); err != nil {
t.Fatalf("ERROR: Unable to reposition the status; %v", err)
}
statuses, err := kanban.StatusList()
if err != nil {
t.Fatalf("ERROR: an error was received when attempting to get the list of statuses from the database; %v", err)
}
gotPositions := make(map[int]string)
for _, status := range statuses {
gotPositions[status.Position] = status.Name
}
if !reflect.DeepEqual(wantPositions, gotPositions) {
t.Errorf("%s\tUnexpected positions received from the database; want: %v, got %v", failure, wantPositions, gotPositions)
} else {
t.Logf("%s\tExpected positions received from the database; got %v", success, gotPositions)
}
}
}
func testMoveCardToStatus(kanban board.Board) func(t *testing.T) {
return func(t *testing.T) {
t.Log("When moving a card between statuses.")
title := "Test card."
cardArgs := board.CardArgs{NewTitle: title, NewDescription: ""}
cardID, err := kanban.CreateCard(cardArgs)
if err != nil {
t.Fatalf("ERROR: Unable to create the card in the database, %v", err)
}
statusList, err := kanban.StatusList()
if err != nil {
t.Fatalf("ERROR: Unable to retrieve the list of statuses from the database, %v", err)
}
status0, status2 := statusList[0], statusList[2]
moveArgs := board.MoveToStatusArgs{CardID: cardID, CurrentStatusID: status0.ID, NextStatusID: status2.ID}
if err := kanban.MoveToStatus(moveArgs); err != nil {
t.Fatalf("ERROR: Unable to move the Card ID from '%s' to '%s', %v", status0.Name, status2.Name, err)
} else {
t.Logf("%s\tThe MoveToStatus operation has completed without error.", success)
}
t.Logf("\tVerifying that the card has moved to '%s'...", status2.Name)
statusList, err = kanban.StatusList()
if err != nil {
t.Fatalf("ERROR: Unable to retrieve the list of statuses from the database, %v", err)
}
status0, status2 = statusList[0], statusList[2]
if len(status0.CardIds) != 0 {
t.Errorf("%s\tUnexpected number of card IDs found in '%s', want: 0, got: %d", failure, status0.Name, len(status0.CardIds))
} else {
t.Logf("%s\tThe number of card IDs in '%s' is now %d", success, status0.Name, len(status0.CardIds))
}
if len(status2.CardIds) != 1 {
t.Errorf("%s\tUnexpected number of card IDs found in '%s', want: 1, got: %d", failure, status2.Name, len(status2.CardIds))
} else {
t.Logf("%s\tThe number of card IDs in '%s' is now %d", success, status2.Name, len(status2.CardIds))
}
card, err := kanban.Card(status2.CardIds[0])
if err != nil {
t.Fatalf("ERROR: Unable to retrieve the card from the database, %v", err)
}
if card.Title != title {
t.Errorf("%s\tUnexpected card title found in '%s', want: '%s', got: '%s'", success, status2.Name, title, card.Title)
} else {
t.Logf("%s\tExpected card title found in '%s', got: '%s'", success, status2.Name, card.Title)
}
}
}
func testDeleteEmptyStatus(kanban board.Board, statusID int, wantPositions map[int]string) func(t *testing.T) {
return func(t *testing.T) {
t.Log("When deleting an empty status.")
err := kanban.DeleteStatus(statusID)
if err != nil {
t.Fatalf("ERROR: an error was received when attempting to delete the status from the database; %v", err)
}
statuses, err := kanban.StatusList()
if err != nil {
t.Fatalf("ERROR: an error was received when attempting to get the list of statuses from the database; %v", err)
}
gotPositions := make(map[int]string)
for _, status := range statuses {
gotPositions[status.Position] = status.Name
}
if !reflect.DeepEqual(wantPositions, gotPositions) {
t.Errorf("%s\tUnexpected positions received from the database; want: %v, got %v", failure, wantPositions, gotPositions)
} else {
t.Logf("%s\tExpected positions received from the database; got %v", success, gotPositions)
}
}
}
func testDeleteNonEmptyStatus(kanban board.Board, statusID int) func(t *testing.T) {
return func(t *testing.T) {
t.Log("When deleting a non-empty status.")
err := kanban.DeleteStatus(statusID)
switch {
case err == nil:
t.Errorf("%s\tExpected an error for deleting a non-empty status but received 'nil'", failure)
case errors.As(err, &board.StatusNotEmptyError{}):
t.Logf("%s\tExpected error received after attempting to delete a non-empty status; got: '%v'", success, err)
default:
t.Errorf("%s\tUnexpected error received after attempting to delete a non-empty status; got: '%v'", failure, err)
}
}
}
func testReadNonExistentStatus(kanban board.Board, statusID int) func(t *testing.T) {
return func(t *testing.T) {
t.Log("When attempting to retrieving a non-existent status from the database.")
_, err := kanban.Status(statusID)
switch {
case err == nil:
t.Errorf("%s\tWanted an error for trying to retrieve a non-existent status, but got 'nil' instead.", failure)
case errors.As(err, &board.StatusNotExistError{}):
t.Logf("%s\tExpected error received after attempting to retrieve a non-existent status from the database; got '%v'", success, err)
default:
t.Errorf("%s\tUnexpected error received after attempting to retrieve a non-existent status from the database; got '%v'", failure, err)
}
}
}