package board_test import ( "errors" "os" "path/filepath" "reflect" "sort" "testing" "time" "codeflow.dananglin.me.uk/apollo/pelican/internal/board" ) func TestCardLifecycle(t *testing.T) { t.Log("Testing the lifecycle of a couple of cards...") projectDir, err := projectRoot() if err != nil { t.Fatalf(err.Error()) } testDBPath := filepath.Join(projectDir, "test", "databases", "Board_TestCardLifecycle.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, let us create a couple of cards...") cardArgs := map[int]board.CardArgs{ 1: { NewTitle: "Test Card 01", NewDescription: "Ensure that the cards are safely stored in the database.", }, 2: { NewTitle: "Test Card 02", NewDescription: "Ensure that cards can be modified and that the modifications are saved in the database.", }, 3: { NewTitle: "Test Card 03", NewDescription: "Ensure that cards can be deleted from the database.", }, } timestamp := time.Now().Format(time.DateTime) t.Run("Test Create Cards", testCreateCards(kanban, cardArgs)) t.Logf("Now that we've created some cards, let's retrieve one from the database...") t.Run("Test Read One Card", testReadOneCard(kanban, 1, cardArgs[1], timestamp)) t.Logf("Let us try retrieving multiple cards...") subCardArgs := make(map[int]board.CardArgs) subCardArgs[2] = cardArgs[2] subCardArgs[3] = cardArgs[3] t.Run("Test Read Multiple Cards", testReadManyCards(kanban, subCardArgs)) t.Logf("Let's see what happens when trying to read a card that does not exist in the database...") t.Run("Test Read Non-Existent Card", testReadNonExistentCard(kanban, 1000)) t.Logf("Let us try modifying the title and description of one of the cards...") modifiedCardArgs := board.UpdateCardArgs{ CardID: 1, CardArgs: board.CardArgs{ NewTitle: cardArgs[1].NewTitle + " (edited).", NewDescription: "This description has been edited.", }, } t.Run("Test Update Card", testUpdateCard(kanban, modifiedCardArgs, timestamp)) t.Logf("Let us also try only modifying the description of a card...") expectedCardContent := "Only the description is updated in this card." modifiedCardArgs = board.UpdateCardArgs{ CardID: 2, CardArgs: board.CardArgs{ NewTitle: "", NewDescription: expectedCardContent, }, } expectedCardTitle := "Test Card 02" t.Run("Test Update Card Content Only", testUpdateCardContent(kanban, modifiedCardArgs, expectedCardTitle, expectedCardContent, timestamp)) t.Logf("Let's now delete a card from the database...") t.Run("Test Card Delete", testDeleteCard(kanban, 1, 1, []int{2, 3})) } func testCreateCards(kanban board.Board, args map[int]board.CardArgs) func(t *testing.T) { return func(t *testing.T) { t.Log("When creating and saving the cards to the database.") for i := 1; i <= len(args); i++ { if _, err := kanban.CreateCard(args[i]); err != nil { t.Fatalf("ERROR: Unable to create the test card, %s.", err) } } t.Logf("%s\tAll cards have been saved to the database.", success) t.Logf("\tVerifying that the card IDs are present the status in first position...") statusList, err := kanban.StatusList() if err != nil { t.Fatalf("ERROR: Unable to retrieve the list of statuses; %v", err) } if len(statusList) == 0 { t.Fatal("ERROR: No statuses were returned from the database.") } expectedStatus := statusList[0] wantCardIDs := []int{1, 2, 3} if !reflect.DeepEqual(expectedStatus.CardIds, wantCardIDs) { t.Errorf("%s\tUnexpected card IDs found in the expected status, want: %v, got %v.", failure, wantCardIDs, expectedStatus.CardIds) } else { t.Logf("%s\tExpected card IDs found in the expected status, got %v.", success, expectedStatus.CardIds) } } } func testReadOneCard(kanban board.Board, cardID int, cardArgs board.CardArgs, wantTimestamp string) func(t *testing.T) { return func(t *testing.T) { t.Log("When reading a card from the database.") card, err := kanban.Card(cardID) if err != nil { t.Fatalf("ERROR: Unable to read test card, %s.", err) } wantTitle := cardArgs.NewTitle if card.Title != wantTitle { t.Errorf("%s\tUnexpected card title received, want: %q, got: %q.", failure, wantTitle, card.Title) } else { t.Logf("%s\tExpected card title received, got: %q.", success, card.Title) } wantDescription := cardArgs.NewDescription if card.Description != cardArgs.NewDescription { t.Errorf("%s\tUnexpected card content received, want: %q, got: %q.", failure, wantDescription, card.Description) } else { t.Logf("%s\tExpected card content received, got: %q.", success, card.Description) } if card.Created != wantTimestamp { t.Errorf("%s\tUnexpected timestamp received for the created card, want: %q, got %q.", failure, wantTimestamp, card.Created) } else { t.Logf("%s\tExpected timestamp received for the created card, got: %q.", success, card.Created) } } } func testReadManyCards(kanban board.Board, cardArgs map[int]board.CardArgs) func(t *testing.T) { return func(t *testing.T) { t.Log("When reading multiple cards from the database.") var cardIDList []int for key := range cardArgs { cardIDList = append(cardIDList, key) } cards, err := kanban.CardList(cardIDList) if err != nil { t.Fatalf("ERROR: Unable to retrieve the list of cards from the database; %v", err) } for i := range cards { cardID := cards[i].GetID() wantTitle := cardArgs[cardID].NewTitle gotTitle := cards[i].Title if wantTitle != gotTitle { t.Errorf("%s\tUnexpected card title received, want: %q, got: %q.", failure, wantTitle, gotTitle) } else { t.Logf("%s\tExpected card title received, got: %q.", success, gotTitle) } } } } func testReadNonExistentCard(kanban board.Board, cardID int) func(t *testing.T) { return func(t *testing.T) { t.Log("When attempting to retrieving a non-existent card from the database.") _, err := kanban.Card(cardID) switch { case err == nil: t.Errorf("%s\tWanted an error for retrieving a non-existent card but got 'nil' instead.", failure) case errors.As(err, &board.CardNotExistError{}): t.Logf("%s\tExpected error received after attempting to retrieve a non-existent card from the database; got '%v'", success, err) default: t.Errorf("%s\tUnexpected error received after attempting to retrieve a non-existent card from the database; got: '%v'", failure, err) } } } func testUpdateCard(kanban board.Board, modifiedCardArgs board.UpdateCardArgs, timestamp string) func(t *testing.T) { return func(t *testing.T) { t.Log("When a card is updated in the database.") if err := kanban.UpdateCard(modifiedCardArgs); err != nil { t.Fatalf("ERROR: Unable to update the test card, %s", err) } got, err := kanban.Card(modifiedCardArgs.CardID) if err != nil { t.Fatalf("ERROR: Unable to read the modified test card, %s", err) } want := board.Card{ Identity: board.Identity{ID: modifiedCardArgs.CardID}, Title: modifiedCardArgs.NewTitle, Description: modifiedCardArgs.NewDescription, Created: timestamp, } if !reflect.DeepEqual(got, want) { t.Errorf("%s\tUnexpected card read from the database: want %+v, got %+v", failure, want, got) } else { t.Logf("%s\tExpected card read from the database: got %+v", success, got) } } } func testUpdateCardContent(kanban board.Board, modifiedCardArgs board.UpdateCardArgs, expectedTitle, expectedDescription, timestamp string) func(t *testing.T) { return func(t *testing.T) { t.Log("When (and only when) a card's content is updated in the database.") if err := kanban.UpdateCard(modifiedCardArgs); err != nil { t.Fatalf("ERROR: Unable to update the test card, %s", err) } got, err := kanban.Card(modifiedCardArgs.CardID) if err != nil { t.Fatalf("ERROR: Unable to read the modified test card, %s", err) } want := board.Card{ Identity: board.Identity{ID: modifiedCardArgs.CardID}, Title: expectedTitle, Description: expectedDescription, Created: timestamp, } if !reflect.DeepEqual(got, want) { t.Errorf("%s\tUnexpected card read from the database, want: %+v, got: %+v", failure, want, got) } else { t.Logf("%s\tExpected card read from the database, got: %+v", success, got) } } } func testDeleteCard(kanban board.Board, cardID, statusID int, expectedCardIDs []int) func(t *testing.T) { return func(t *testing.T) { t.Log("When deleting a card from the database.") args := board.DeleteCardArgs{ CardID: cardID, StatusID: statusID, } if err := kanban.DeleteCard(args); err != nil { t.Fatalf("ERROR: An error occurred when deleting the card from the database, %v", err) } else { t.Logf("%s\tNo errors occurred when deleting the card from the database.", success) } t.Logf("\tVerifying that the card is removed from the database...") _, err := kanban.Card(cardID) switch { case err == nil: t.Errorf("%s\tDid not receive the expected error when attempting to read the deleted card.", failure) case errors.As(err, &board.CardNotExistError{}): t.Logf("%s\tSuccessfully received board.CardNotExistError when attempting to retrieve the deleted card.", success) default: t.Errorf( "%s\tDid not receive the expected error when attempting to retrieve the deleted card; got '%v'.", failure, err, ) } t.Logf("\tVerifying that the card's ID is removed from the status list...") status, err := kanban.Status(statusID) if err != nil { t.Fatalf("ERROR: Unable to read status '%d' from the database; %v", statusID, err) } sort.Ints(expectedCardIDs) sort.Ints(status.CardIds) if !reflect.DeepEqual(status.CardIds, expectedCardIDs) { t.Errorf("%s\tUnexpected list of card IDs found in status '%d'; want %v, got %v", failure, statusID, expectedCardIDs, status.CardIds) } else { t.Logf("%s\tExpected list of card IDs found in status '%d'; got %v", success, statusID, status.CardIds) } } }