feat: bug fixes, basic set of tests
This commit is contained in:
parent
4a7491756a
commit
fb9971a32c
@ -13,6 +13,7 @@ namespace Database
|
||||
[Name] TEXT NOT NULL,
|
||||
[YearOfRelease] INTEGER,
|
||||
[Total] INTEGER,
|
||||
[Available] INTEGER,
|
||||
[AuthorID] INTEGER,
|
||||
[PublisherID] INTEGER,
|
||||
FOREIGN KEY ([AuthorID]) REFERENCES [Authors]([ID]),
|
||||
@ -44,7 +45,7 @@ namespace Database
|
||||
[Name] TEXT,
|
||||
[Surname] TEXT
|
||||
);";
|
||||
var command = conn.CreateCommand();
|
||||
SqliteCommand command = conn.CreateCommand();
|
||||
command.CommandText = createTableQuery;
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Normalizace/**/*.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
28
Program.cs
28
Program.cs
@ -11,35 +11,9 @@ public class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
string db = "Database.sqlite";
|
||||
string connectionString = "Data Source=" + db;
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = new SqliteConnection(connectionString))
|
||||
{
|
||||
connection.Open();
|
||||
try
|
||||
{
|
||||
Database.Database.CreateDatabaseQuery(connection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("Database already exists.");
|
||||
}
|
||||
|
||||
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
// implment stuff here idc
|
||||
AuthorDto author = Team2.Team2.AddAuthor(connection, "John", "Doe", new DateTime(1990, 1, 1));
|
||||
AuthorDto getAuthor = Team4.Team4.GetAuthorById(connection, author.Id);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Tests.TestRunner.RunAll();
|
||||
}
|
||||
catch (SqliteException ex)
|
||||
{
|
||||
|
||||
89
docs/test.md
89
docs/test.md
@ -16,3 +16,92 @@ Prazdne inputy od uzivatele
|
||||
Consecutive r/w
|
||||
Type checking
|
||||
Fuzzing inputu
|
||||
|
||||
-----------------------------------------
|
||||
|
||||
Scénáře (přidání knihy) + očekávané výstupy
|
||||
|
||||
- Předpoklady testů:
|
||||
- Existuje `AuthorID` a `PublisherID` v DB (jinak přidání knihy selže a funkce vrátí null).
|
||||
- Implementace `Team2.AddBook` vkládá přes `INSERT OR IGNORE` a následně dělá `SELECT ... LIMIT 1` podle kombinace `(Name, AuthorID, PublisherID, YearOfRelease)`.
|
||||
- Poznámka ke schématu: pokud tabulka `Books` NEOBSAHUJE sloupec `Available`, vkládání selže SQL chybou a funkce vrátí null. Pokud `Available` existuje (v předem připravené DB), testy s `Available` dávají smysl.
|
||||
|
||||
- Základní flow – úspěch:
|
||||
- Vstup: validní `name`, existující `authorId`, existující `publisherId`, rozumný `year`, `total >= 0`, `available >= 0`.
|
||||
- Očekávaný výstup: `BookDto` s nenulovým `Id`, hodnotami dle vstupu; návrat nenull.
|
||||
|
||||
- Neexistující autor:
|
||||
- Vstup: `authorId` neexistuje v `Authors`.
|
||||
- Očekávaný výstup: nenastane insert, funkce vrátí `null`.
|
||||
|
||||
- Neexistující publisher:
|
||||
- Vstup: `publisherId` neexistuje v `Publisher`.
|
||||
- Očekávaný výstup: nenastane insert, funkce vrátí `null`.
|
||||
|
||||
- Duplicita knihy (stejný `name` + `authorId` + `publisherId` + `year`):
|
||||
- Pokud v DB existuje unikátní omezení na tuto kombinaci: `INSERT OR IGNORE` neprovede insert a `SELECT ... LIMIT 1` vrátí existující řádek ⇒ `BookDto` (nenull), nezdvojené data.
|
||||
- Pokud v DB NEexistuje žádné unikátní omezení (výchozí schema v `Database.cs` ho nemá): dojde ke vložení DUPLIKÁTNÍHO řádku, `SELECT ... LIMIT 1` vrátí libovolný první shodný záznam ⇒ `BookDto` (nenull), ale data jsou duplicitní v tabulce.
|
||||
|
||||
- `name` prázdný řetězec:
|
||||
- `Books.Name` je `NOT NULL`, ale prázdný řetězec NENÍ `NULL` ⇒ vložení projde.
|
||||
- Očekávaný výstup: `BookDto` (nenull).
|
||||
|
||||
- `name` = null:
|
||||
- Při předání `null` jako parametru může dojít k chybě binderu parametrů nebo k pokusu vložit `NULL` do `NOT NULL` sloupce.
|
||||
- Očekávaný výstup: SQL nebo parametrická výjimka uvnitř metody ⇒ metoda chytá výjimku a vrátí `null`.
|
||||
|
||||
- `year` záporný nebo 0:
|
||||
- Není validováno na aplikační úrovni ani ve schématu.
|
||||
- Očekávaný výstup: vložení projde ⇒ `BookDto` (nenull).
|
||||
|
||||
- `total` záporné číslo:
|
||||
- Není validováno; ve schématu žádné omezení.
|
||||
- Očekávaný výstup: vložení projde ⇒ `BookDto` (nenull). Pozn.: následné reporty mohou dávat nelogické výsledky.
|
||||
|
||||
- `available` > `total`:
|
||||
- Není validováno; pokud sloupec `Available` v DB existuje, vloží se bez kontroly.
|
||||
- Očekávaný výstup: `BookDto` (nenull).
|
||||
|
||||
- `available` záporné:
|
||||
- Není validováno; pokud sloupec existuje, vloží se.
|
||||
- Očekávaný výstup: `BookDto` (nenull).
|
||||
|
||||
- Extrémně dlouhé `name`:
|
||||
- Bez omezení délky; SQLite uloží. Může mít dopad na výkon a čitelnost.
|
||||
- Očekávaný výstup: `BookDto` (nenull).
|
||||
|
||||
- Speciální znaky / SQL injection v `name`:
|
||||
- Parametrizované dotazy ⇒ bezpečné vůči SQLi.
|
||||
- Očekávaný výstup: `BookDto` (nenull).
|
||||
|
||||
- Současné (konkurenční) vkládání stejné knihy:
|
||||
- Bez unikátního indexu hrozí race condition ⇒ vícenásobné duplicity.
|
||||
- Očekávaný výstup: více identických řádků v `Books`.
|
||||
|
||||
- Nesoulad schématu: chybějící `Available` ve `Books`:
|
||||
- `INSERT` obsahuje `Available` sloupec; pokud DB nemá `Available`, dojde k SQL chybě.
|
||||
- Očekávaný výstup: metoda vrátí `null`.
|
||||
|
||||
-----------------------------------------
|
||||
|
||||
Scénáře (autor) – “stejný autor včetně DOB”
|
||||
|
||||
- Vytvoření autora se stejným `Name` + `Surname` + `DateOfBirth` opakovaně:
|
||||
- Pokud existuje unikátní omezení nad (Name, Surname, DateOfBirth): `INSERT OR IGNORE` neprovede insert ⇒ vrácen bude existující autor (nenull).
|
||||
- Pokud unikát neexistuje (výchozí schema v `Database.cs` jej nemá): vloží se DUPLIKÁT; následný `SELECT ... LIMIT 1` vrátí některého z nich ⇒ `AuthorDto` (nenull), ale v DB jsou duplicity.
|
||||
|
||||
- Přidání knihy k “stejnému autorovi”:
|
||||
- Logika knihy pracuje výhradně s `authorId`. Pokud existují 2 různé řádky autora se stejnými údaji (Name/Surname/DOB), ale rozdílným `ID`, přidání knihy proběhne ke kterékoliv instanci podle zvoleného `authorId`.
|
||||
- Očekávaný výstup: `BookDto` (nenull), navázáno na konkrétní `AuthorID`, nikoli na shodu jména/DOB.
|
||||
|
||||
-----------------------------------------
|
||||
|
||||
Edge cases (souhrn)
|
||||
|
||||
- Chybějící cizí klíče (`AuthorID`, `PublisherID`) ⇒ návrat `null`.
|
||||
- Duplicity bez unikátních indexů ⇒ v DB vznikají vícenásobné identické záznamy.
|
||||
- Schéma vs. kód: `Available` ve `Books` – pokud chybí, přidání knihy vždy selže ⇒ `null`.
|
||||
- `Name` prázdný vs `NULL`: prázdný projde, `NULL` vyvolá chybu kvůli `NOT NULL`.
|
||||
- Hodnoty mimo rozsah (záporné `year`/`total`/`available`) nejsou validovány.
|
||||
- Neprobíhá automatické snižování zásob po výpůjčce (`Borrows` neupravují `Books.Total/Available`).
|
||||
- Potenciálně nekonzistentní pluralita tabulek v jiných částech (např. `Publishers` vs `Publisher`) – neovlivňuje `Team2.AddBook`, ale je rizikem v integračních testech.
|
||||
|
||||
572
src/Tests.cs
Normal file
572
src/Tests.cs
Normal file
@ -0,0 +1,572 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Database.Dto;
|
||||
using Team2;
|
||||
using Team3;
|
||||
using Team4;
|
||||
|
||||
namespace Tests
|
||||
{
|
||||
public static class TestRunner
|
||||
{
|
||||
private static int _passed;
|
||||
private static int _failed;
|
||||
|
||||
public static void RunAll()
|
||||
{
|
||||
_passed = 0;
|
||||
_failed = 0;
|
||||
|
||||
Run(Test_AddBook_Success);
|
||||
Run(Test_AddBook_MissingAuthor_Fails);
|
||||
Run(Test_AddBook_MissingPublisher_Fails);
|
||||
Run(Test_AddBook_Duplicate_AllowsWithoutUniqueIndex);
|
||||
Run(Test_AddBook_EmptyName_Succeeds);
|
||||
Run(Test_AddBook_NullName_Fails);
|
||||
Run(Test_AddBook_NegativeValues_Succeeds);
|
||||
Run(Test_AddBook_AvailableGreaterThanTotal_Succeeds);
|
||||
Run(Test_Author_DuplicateSameDOB_AllowedWithoutUniqueIndex);
|
||||
Run(Test_AddAuthor_EmptyName_Succeeds_And_NullName_Fails);
|
||||
Run(Test_AddPublisher_Success_Duplicates_And_NullName_Fails);
|
||||
Run(Test_AddUser_Success_And_DuplicatesAllowed);
|
||||
Run(Test_Borrow_Success);
|
||||
Run(Test_Borrow_DueDate_Default_30Days);
|
||||
Run(Test_Borrow_MissingUser_Fails);
|
||||
Run(Test_Borrow_MissingBook_Fails);
|
||||
Run(Test_GetBooksByName_MultipleMatches);
|
||||
Run(Test_GetBooksByAuthor_ReturnsBooks);
|
||||
Run(Test_GetBooksByAvailableBooks_Filter);
|
||||
Run(Test_StolenBooks_Scenarios);
|
||||
Run(Test_StolenBooks_None_ReturnsEmpty);
|
||||
Run(Test_GetAuthorById_Success_And_NotFound);
|
||||
Run(Test_Team4_Methods_Throw_On_ClosedConnection);
|
||||
Run(Test_Team4_All_Methods_Throw_On_ClosedConnection);
|
||||
Run(Test_Team4_NotImplemented_Methods_Throw);
|
||||
Run(Test_EditAuthor_Success_And_NotFound);
|
||||
Run(Test_EditPublisher_Success_And_NotFound);
|
||||
Run(Test_EditUser_Success_And_NotFound);
|
||||
Run(Test_EditBook_Success_And_Failures);
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine($"Tests finished. Passed: {_passed}, Failed: {_failed}");
|
||||
}
|
||||
|
||||
private static void Run(Action test)
|
||||
{
|
||||
string name = test.Method.Name;
|
||||
try
|
||||
{
|
||||
test();
|
||||
_passed++;
|
||||
Console.WriteLine($"[PASS] {name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_failed++;
|
||||
Console.WriteLine($"[FAIL] {name}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static SqliteConnection NewMemoryConnection()
|
||||
{
|
||||
SqliteConnection conn = new SqliteConnection("Data Source=:memory:");
|
||||
conn.Open();
|
||||
Database.Database.CreateDatabaseQuery(conn);
|
||||
return conn;
|
||||
}
|
||||
|
||||
private static int CountBooks(SqliteConnection conn, string name, int authorId, int publisherId, int year)
|
||||
{
|
||||
using (SqliteCommand cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = @"SELECT COUNT(*) FROM Books
|
||||
WHERE Name = @name AND AuthorID = @aid AND PublisherID = @pid AND YearOfRelease = @year;";
|
||||
cmd.Parameters.AddWithValue("@name", name);
|
||||
cmd.Parameters.AddWithValue("@aid", authorId);
|
||||
cmd.Parameters.AddWithValue("@pid", publisherId);
|
||||
cmd.Parameters.AddWithValue("@year", year);
|
||||
return Convert.ToInt32(cmd.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
|
||||
private static int CountAuthors(SqliteConnection conn, string name, string surname, DateTime dob)
|
||||
{
|
||||
using (SqliteCommand cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = @"SELECT COUNT(*) FROM Authors
|
||||
WHERE Name = @name AND Surname = @surname AND DateOfBirth = @dob;";
|
||||
cmd.Parameters.AddWithValue("@name", name);
|
||||
cmd.Parameters.AddWithValue("@surname", surname);
|
||||
cmd.Parameters.AddWithValue("@dob", dob.ToString("yyyy-MM-dd"));
|
||||
return Convert.ToInt32(cmd.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
|
||||
private static int CountUsers(SqliteConnection conn, string name, string surname)
|
||||
{
|
||||
using (SqliteCommand cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = @"SELECT COUNT(*) FROM Users WHERE Name = @name AND Surname = @surname;";
|
||||
cmd.Parameters.AddWithValue("@name", name);
|
||||
cmd.Parameters.AddWithValue("@surname", surname);
|
||||
return Convert.ToInt32(cmd.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
|
||||
private static int CountBorrowsForUserBook(SqliteConnection conn, int userId, int bookId)
|
||||
{
|
||||
using (SqliteCommand cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = @"SELECT COUNT(*) FROM Borrows WHERE UserID = @uid AND BookID = @bid;";
|
||||
cmd.Parameters.AddWithValue("@uid", userId);
|
||||
cmd.Parameters.AddWithValue("@bid", bookId);
|
||||
return Convert.ToInt32(cmd.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
|
||||
private static Publisher EnsurePublisher(SqliteConnection conn, string name = "Penguin", string state = "US")
|
||||
{
|
||||
Publisher p = Team2.Team2.AddPublisher(conn, name, state);
|
||||
if (p == null || p.Id <= 0) throw new Exception("Publisher creation failed");
|
||||
return p;
|
||||
}
|
||||
|
||||
private static AuthorDto EnsureAuthor(SqliteConnection conn, string name = "John", string surname = "Doe", int year = 1990, int month = 1, int day = 1)
|
||||
{
|
||||
AuthorDto a = Team2.Team2.AddAuthor(conn, name, surname, new DateTime(year, month, day));
|
||||
if (a == null || a.Id <= 0) throw new Exception("Author creation failed");
|
||||
return a;
|
||||
}
|
||||
|
||||
private static UserDto EnsureUser(SqliteConnection conn, string name = "Alice", string surname = "Smith")
|
||||
{
|
||||
UserDto u = Team2.Team2.AddUser(conn, name, surname);
|
||||
if (u == null || u.Id <= 0) throw new Exception("User creation failed");
|
||||
return u;
|
||||
}
|
||||
|
||||
private static BookDto EnsureBook(SqliteConnection conn, string title = "Sample Book", int year = 2001, int total = 3, int available = 3)
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
BookDto book = Team2.Team2.AddBook(conn, title, author.Id, publisher.Id, year, total, available);
|
||||
if (book == null || book.Id <= 0) throw new Exception("Book creation failed");
|
||||
return book;
|
||||
}
|
||||
|
||||
// Tests
|
||||
|
||||
// Team2 - Books
|
||||
private static void Test_AddBook_Success()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
BookDto book = Team2.Team2.AddBook(conn, "Clean Code", author.Id, publisher.Id, 2008, 10, 10);
|
||||
if (book == null || book.Id <= 0) throw new Exception("Expected book to be created");
|
||||
if (book.Name != "Clean Code") throw new Exception("Name mismatch");
|
||||
if (book.YearOfRelease != 2008) throw new Exception("Year mismatch");
|
||||
if (book.Total != 10) throw new Exception("Total mismatch");
|
||||
int cnt = CountBooks(conn, "Clean Code", author.Id, publisher.Id, 2008);
|
||||
if (cnt != 1) throw new Exception("Expected exactly one row for the book");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddBook_MissingAuthor_Fails()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
BookDto book = Team2.Team2.AddBook(conn, "No Author Book", 99999, publisher.Id, 2020, 1, 1);
|
||||
if (book != null) throw new Exception("Expected null when author does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddBook_MissingPublisher_Fails()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
BookDto book = Team2.Team2.AddBook(conn, "No Publisher Book", author.Id, 99999, 2020, 1, 1);
|
||||
if (book != null) throw new Exception("Expected null when publisher does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddBook_Duplicate_AllowsWithoutUniqueIndex()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
BookDto b1 = Team2.Team2.AddBook(conn, "Duplicate", author.Id, publisher.Id, 2021, 2, 2);
|
||||
if (b1 == null) throw new Exception("First insert failed");
|
||||
BookDto b2 = Team2.Team2.AddBook(conn, "Duplicate", author.Id, publisher.Id, 2021, 2, 2);
|
||||
if (b2 == null) throw new Exception("Second insert unexpectedly failed");
|
||||
int cnt = CountBooks(conn, "Duplicate", author.Id, publisher.Id, 2021);
|
||||
if (cnt < 2) throw new Exception("Expected duplicates without a unique index");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddBook_EmptyName_Succeeds()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
BookDto book = Team2.Team2.AddBook(conn, "", author.Id, publisher.Id, 2000, 1, 1);
|
||||
if (book == null) throw new Exception("Empty string name should be allowed (NOT NULL != empty)");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddBook_NullName_Fails()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
#pragma warning disable CS8625, CS8604, CS8600
|
||||
BookDto book = Team2.Team2.AddBook(conn, (string)(object)null, author.Id, publisher.Id, 2000, 1, 1);
|
||||
#pragma warning restore CS8625, CS8604, CS8600
|
||||
if (book != null) throw new Exception("Expected null when inserting NULL into NOT NULL column");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddBook_NegativeValues_Succeeds()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
BookDto book = Team2.Team2.AddBook(conn, "NegativeVals", author.Id, publisher.Id, -1, -5, -2);
|
||||
if (book == null) throw new Exception("Negative values should not be validated at DB level here");
|
||||
if (book.YearOfRelease != -1) throw new Exception("Year not stored as provided");
|
||||
if (book.Total != -5) throw new Exception("Total not stored as provided");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddBook_AvailableGreaterThanTotal_Succeeds()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn);
|
||||
Publisher publisher = EnsurePublisher(conn);
|
||||
BookDto book = Team2.Team2.AddBook(conn, "AvailGtTotal", author.Id, publisher.Id, 2010, 1, 5);
|
||||
if (book == null) throw new Exception("Available > Total currently not validated; insert should succeed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_Author_DuplicateSameDOB_AllowedWithoutUniqueIndex()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
string name = "Jane";
|
||||
string surname = "Doe";
|
||||
DateTime dob = new DateTime(1995, 5, 5);
|
||||
AuthorDto a1 = Team2.Team2.AddAuthor(conn, name, surname, dob);
|
||||
if (a1 == null) throw new Exception("First author insert failed");
|
||||
AuthorDto a2 = Team2.Team2.AddAuthor(conn, name, surname, dob);
|
||||
if (a2 == null) throw new Exception("Second author insert unexpectedly failed");
|
||||
int cnt = CountAuthors(conn, name, surname, dob);
|
||||
if (cnt < 2) throw new Exception("Expected duplicate authors without a unique index");
|
||||
}
|
||||
}
|
||||
|
||||
// Team2 - Users
|
||||
private static void Test_AddAuthor_EmptyName_Succeeds_And_NullName_Fails()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
// Empty name should be allowed (NOT NULL != empty)
|
||||
AuthorDto a1 = Team2.Team2.AddAuthor(conn, "", "S", new DateTime(1990, 1, 1));
|
||||
if (a1 == null || a1.Id <= 0) throw new Exception("Empty author name should be allowed");
|
||||
// Null name should fail and return null
|
||||
#pragma warning disable CS8625, CS8604, CS8600
|
||||
AuthorDto a2 = Team2.Team2.AddAuthor(conn, (string)(object)null, "S", new DateTime(1990, 1, 1));
|
||||
#pragma warning restore CS8625, CS8604, CS8600
|
||||
if (a2 != null) throw new Exception("Expected null when inserting NULL author name");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddPublisher_Success_Duplicates_And_NullName_Fails()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
Publisher p1 = Team2.Team2.AddPublisher(conn, "PubX", "CZ");
|
||||
if (p1 == null || p1.Id <= 0) throw new Exception("Publisher insert failed");
|
||||
Publisher p2 = Team2.Team2.AddPublisher(conn, "PubX", "CZ");
|
||||
if (p2 == null || p2.Id <= 0) throw new Exception("Duplicate publisher insert unexpectedly failed");
|
||||
#pragma warning disable CS8625, CS8604, CS8600
|
||||
Publisher p3 = Team2.Team2.AddPublisher(conn, (string)(object)null, "CZ");
|
||||
#pragma warning restore CS8625, CS8604, CS8600
|
||||
if (p3 != null) throw new Exception("Expected null when inserting NULL publisher name");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_AddUser_Success_And_DuplicatesAllowed()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
UserDto u1 = Team2.Team2.AddUser(conn, "Bob", "Brown");
|
||||
if (u1 == null || u1.Id <= 0) throw new Exception("User insert failed");
|
||||
UserDto u2 = Team2.Team2.AddUser(conn, "Bob", "Brown");
|
||||
if (u2 == null || u2.Id <= 0) throw new Exception("Duplicate user insert unexpectedly failed");
|
||||
int cnt = CountUsers(conn, "Bob", "Brown");
|
||||
if (cnt < 2) throw new Exception("Expected duplicate users without a unique index");
|
||||
}
|
||||
}
|
||||
|
||||
// Team2 - Borrow
|
||||
private static void Test_Borrow_Success()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
UserDto user = EnsureUser(conn);
|
||||
BookDto book = EnsureBook(conn, "Borrowed Book", 2015, 5, 5);
|
||||
DateTime borrowed = DateTime.UtcNow.Date;
|
||||
Borrow borrow = Team2.Team2.AddBorrowedBookRecord(conn, book.Id, user.Id, borrowed);
|
||||
if (borrow == null || borrow.Id <= 0) throw new Exception("Borrow creation failed");
|
||||
if (borrow.User == null || borrow.User.Id != user.Id) throw new Exception("Borrow.User mismatch");
|
||||
if (borrow.Book == null || borrow.Book.Id != book.Id) throw new Exception("Borrow.Book mismatch");
|
||||
int cnt = CountBorrowsForUserBook(conn, user.Id, book.Id);
|
||||
if (cnt != 1) throw new Exception("Expected exactly one borrow record");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_Borrow_DueDate_Default_30Days()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
UserDto user = EnsureUser(conn);
|
||||
BookDto book = EnsureBook(conn, "Borrowed Book 2", 2016, 2, 2);
|
||||
DateTime borrowed = new DateTime(2020, 1, 1);
|
||||
Borrow borrow = Team2.Team2.AddBorrowedBookRecord(conn, book.Id, user.Id, borrowed);
|
||||
if (borrow == null) throw new Exception("Borrow creation failed");
|
||||
DateTime expectedDue = borrowed.AddDays(30);
|
||||
if (borrow.ReturnDue.Date != expectedDue.Date) throw new Exception($"ReturnDue mismatch: got {borrow.ReturnDue.Date:yyyy-MM-dd}, expected {expectedDue:yyyy-MM-dd}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_Borrow_MissingUser_Fails()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
BookDto book = EnsureBook(conn);
|
||||
Borrow borrow = Team2.Team2.AddBorrowedBookRecord(conn, book.Id, 999999, DateTime.UtcNow.Date);
|
||||
if (borrow != null) throw new Exception("Expected null when borrowing with missing user");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_Borrow_MissingBook_Fails()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
UserDto user = EnsureUser(conn);
|
||||
Borrow borrow = Team2.Team2.AddBorrowedBookRecord(conn, 999999, user.Id, DateTime.UtcNow.Date);
|
||||
if (borrow != null) throw new Exception("Expected null when borrowing with missing book");
|
||||
}
|
||||
}
|
||||
|
||||
// Team4 - Queries and exceptions
|
||||
private static void Test_GetBooksByName_MultipleMatches()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
BookDto b1 = EnsureBook(conn, "SameName", 2000, 1, 1);
|
||||
BookDto b2 = EnsureBook(conn, "SameName", 2001, 2, 2);
|
||||
List<BookDto> books = Team4.Team4.GetBooksByName(conn, "SameName");
|
||||
if (books == null || books.Count < 2) throw new Exception("Expected at least two books with same name");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_GetBooksByAuthor_ReturnsBooks()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto author = EnsureAuthor(conn, "Auth", "One", 1980, 1, 1);
|
||||
Publisher publisher = EnsurePublisher(conn, "P1", "US");
|
||||
BookDto b1 = Team2.Team2.AddBook(conn, "ByAuthor1", author.Id, publisher.Id, 2010, 1, 1);
|
||||
BookDto b2 = Team2.Team2.AddBook(conn, "ByAuthor2", author.Id, publisher.Id, 2011, 1, 1);
|
||||
List<BookDto> books = Team4.Team4.GetBooksByAuthor(conn, author);
|
||||
if (books == null || books.Count < 2) throw new Exception("Expected two books by author");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_GetBooksByAvailableBooks_Filter()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
BookDto b1 = EnsureBook(conn, "Stock1", 2000, 0, 0);
|
||||
BookDto b2 = EnsureBook(conn, "Stock2", 2001, 2, 2);
|
||||
List<BookDto> books = Team4.Team4.GetBooksByAvailableBooks(conn, 1);
|
||||
if (books == null) throw new Exception("Expected list, got null");
|
||||
bool hasStock2 = false;
|
||||
for (int i = 0; i < books.Count; i++)
|
||||
{
|
||||
if (books[i].Name == "Stock2") { hasStock2 = true; break; }
|
||||
}
|
||||
if (!hasStock2) throw new Exception("Expected Stock2 in results for minStock=1");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_StolenBooks_Scenarios()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
UserDto user = EnsureUser(conn);
|
||||
BookDto book = EnsureBook(conn, "Overdue", 2005, 1, 1);
|
||||
// Borrow far in the past so ReturnDue < now
|
||||
DateTime borrowedPast = DateTime.UtcNow.Date.AddDays(-60);
|
||||
Borrow br = Team2.Team2.AddBorrowedBookRecord(conn, book.Id, user.Id, borrowedPast);
|
||||
if (br == null) throw new Exception("Failed to create overdue borrow");
|
||||
List<BookDto> stolenAll = Team4.Team4.GetStolenBooks(conn);
|
||||
if (stolenAll == null || stolenAll.Count == 0) throw new Exception("Expected stolen books to include the overdue borrow");
|
||||
List<BookDto> stolenByUser = Team4.Team4.GetStolenBooksByUser(conn, user);
|
||||
if (stolenByUser == null || stolenByUser.Count == 0) throw new Exception("Expected stolen books by user");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_StolenBooks_None_ReturnsEmpty()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
List<BookDto> stolenAll = Team4.Team4.GetStolenBooks(conn);
|
||||
if (stolenAll == null) throw new Exception("Expected empty list, got null");
|
||||
if (stolenAll.Count != 0) throw new Exception("Expected no stolen books initially");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_GetAuthorById_Success_And_NotFound()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto a = EnsureAuthor(conn, "Lookup", "Author", 1970, 1, 1);
|
||||
AuthorDto ok = Team4.Team4.GetAuthorById(conn, a.Id);
|
||||
if (ok == null || ok.Id != a.Id) throw new Exception("Expected author lookup success");
|
||||
AuthorDto missing = Team4.Team4.GetAuthorById(conn, 999999);
|
||||
if (missing != null) throw new Exception("Expected null for missing author");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_Team4_Methods_Throw_On_ClosedConnection()
|
||||
{
|
||||
SqliteConnection conn = new SqliteConnection("Data Source=:memory:");
|
||||
conn.Open();
|
||||
Database.Database.CreateDatabaseQuery(conn);
|
||||
conn.Close();
|
||||
bool threw = false;
|
||||
try
|
||||
{
|
||||
List<BookDto> _ = Team4.Team4.GetBooksByName(conn, "Anything");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
if (!threw) throw new Exception("Expected Team4.GetBooksByName to throw on closed connection");
|
||||
}
|
||||
|
||||
private static void Test_Team4_All_Methods_Throw_On_ClosedConnection()
|
||||
{
|
||||
SqliteConnection conn = new SqliteConnection("Data Source=:memory:");
|
||||
conn.Open();
|
||||
Database.Database.CreateDatabaseQuery(conn);
|
||||
conn.Close();
|
||||
string fail(string method) { return $"Expected {method} to throw on closed connection"; }
|
||||
try { Team4.Team4.GetBooksByAuthor(conn, new AuthorDto { Id = 1, Name = "", Surname = "", DateOfBirth = new DateTime(2000, 1, 1) }); throw new Exception(fail("GetBooksByAuthor")); } catch (Exception) { }
|
||||
try { Team4.Team4.GetBooksByAvailableBooks(conn, 1); throw new Exception(fail("GetBooksByAvailableBooks")); } catch (Exception) { }
|
||||
try { Team4.Team4.GetStolenBooks(conn); throw new Exception(fail("GetStolenBooks")); } catch (Exception) { }
|
||||
try { Team4.Team4.GetStolenBooksByUser(conn, new UserDto { Id = 1, Name = "", Surname = "" }); throw new Exception(fail("GetStolenBooksByUser")); } catch (Exception) { }
|
||||
try { Team4.Team4.GetAuthorById(conn, 1); throw new Exception(fail("GetAuthorById")); } catch (Exception) { }
|
||||
}
|
||||
|
||||
private static void Test_Team4_NotImplemented_Methods_Throw()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
bool threw1 = false;
|
||||
try { Team4.Team4.GetBooksBy(conn); } catch (NotImplementedException) { threw1 = true; }
|
||||
if (!threw1) throw new Exception("Expected NotImplementedException from GetBooksBy");
|
||||
bool threw2 = false;
|
||||
try { Team4.Team4.GetBorrowLogBy(conn); } catch (NotImplementedException) { threw2 = true; }
|
||||
if (!threw2) throw new Exception("Expected NotImplementedException from GetBorrowLogBy");
|
||||
}
|
||||
}
|
||||
|
||||
// Team3 - Edit operations
|
||||
private static void Test_EditAuthor_Success_And_NotFound()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
AuthorDto a = EnsureAuthor(conn, "Old", "Name", 1985, 2, 2);
|
||||
a.Name = "New";
|
||||
a.Surname = "Surname";
|
||||
AuthorDto? updated = EditRecords.EditAuthor(conn, a);
|
||||
if (updated == null || updated.Name != "New" || updated.Surname != "Surname") throw new Exception("EditAuthor failed to update");
|
||||
AuthorDto? notFound = EditRecords.EditAuthor(conn, new AuthorDto { Id = 999999, Name = "X", Surname = "Y", DateOfBirth = new DateTime(1990, 1, 1) });
|
||||
if (notFound != null) throw new Exception("Expected null for non-existing author");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_EditPublisher_Success_And_NotFound()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
Publisher p = EnsurePublisher(conn, "OldPub", "UK");
|
||||
p.Name = "NewPub";
|
||||
p.State = "CZ";
|
||||
Publisher? updated = EditRecords.EditPublisher(conn, p);
|
||||
if (updated == null || updated.Name != "NewPub" || updated.State != "CZ") throw new Exception("EditPublisher failed to update");
|
||||
Publisher? notFound = EditRecords.EditPublisher(conn, new Publisher { Id = 999999, Name = "X", State = "Y" });
|
||||
if (notFound != null) throw new Exception("Expected null for non-existing publisher");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_EditUser_Success_And_NotFound()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
UserDto u = EnsureUser(conn, "Carl", "Jr");
|
||||
u.Name = "Carlos";
|
||||
u.Surname = "Senior";
|
||||
UserDto? updated = EditRecords.EditUser(conn, u);
|
||||
if (updated == null || updated.Name != "Carlos" || updated.Surname != "Senior") throw new Exception("EditUser failed to update");
|
||||
UserDto? notFound = EditRecords.EditUser(conn, new UserDto { Id = 999999, Name = "X", Surname = "Y" });
|
||||
if (notFound != null) throw new Exception("Expected null for non-existing user");
|
||||
}
|
||||
}
|
||||
|
||||
private static void Test_EditBook_Success_And_Failures()
|
||||
{
|
||||
using (SqliteConnection conn = NewMemoryConnection())
|
||||
{
|
||||
// Seed original
|
||||
AuthorDto a1 = EnsureAuthor(conn, "A", "One", 1970, 1, 1);
|
||||
Publisher p1 = EnsurePublisher(conn, "Pub1", "US");
|
||||
BookDto b = Team2.Team2.AddBook(conn, "Original", a1.Id, p1.Id, 1999, 1, 1);
|
||||
if (b == null) throw new Exception("Seeding book failed");
|
||||
// Prepare updates
|
||||
AuthorDto a2 = EnsureAuthor(conn, "A", "Two", 1975, 1, 1);
|
||||
Publisher p2 = EnsurePublisher(conn, "Pub2", "DE");
|
||||
b.Name = "Updated";
|
||||
b.Author = a2;
|
||||
b.Publisher = p2;
|
||||
b.YearOfRelease = 2000;
|
||||
b.Total = 5;
|
||||
BookDto? updated = EditRecords.EditBook(conn, b);
|
||||
if (updated == null || updated.Name != "Updated" || updated.Author.Id != a2.Id || updated.Publisher.Id != p2.Id) throw new Exception("EditBook failed to update");
|
||||
// Not found
|
||||
BookDto? notFound = EditRecords.EditBook(conn, new BookDto { Id = 999999, Name = "X", YearOfRelease = 2000, Total = 1, Author = a2, Publisher = p2 });
|
||||
if (notFound != null) throw new Exception("Expected null for non-existing book");
|
||||
// Invalid author
|
||||
BookDto invalidAuthor = new BookDto { Id = b.Id, Name = "X", YearOfRelease = 2001, Total = 1, Author = new AuthorDto { Id = 999999, Name = "Na", Surname = "Na", DateOfBirth = new DateTime(1970, 1, 1) }, Publisher = p2 };
|
||||
if (EditRecords.EditBook(conn, invalidAuthor) != null) throw new Exception("Expected null when editing with non-existing author");
|
||||
// Invalid publisher
|
||||
BookDto invalidPublisher = new BookDto { Id = b.Id, Name = "X", YearOfRelease = 2001, Total = 1, Author = a2, Publisher = new Publisher { Id = 999999, Name = "Na", State = "Na" } };
|
||||
if (EditRecords.EditBook(conn, invalidPublisher) != null) throw new Exception("Expected null when editing with non-existing publisher");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user