diff --git a/Sources/Samples.sln b/Sources/Samples.sln index e750180..bb5bb73 100644 --- a/Sources/Samples.sln +++ b/Sources/Samples.sln @@ -13,14 +13,21 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UTests2", "mocks\UTests2\UT EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UTests3", "mocks\UTests3\UTests3.csproj", "{A9FE0291-58A1-42E5-9768-34B3A33F4116}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "basics", "basics", "{DFAECDF9-BEF0-4D05-B7C7-238D78986BD0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Model", "basics\Model\Model.csproj", "{12D9ED1E-4A8C-4A67-B56D-16D8AF1287F5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stub", "basics\Stub\Stub.csproj", "{E0A3410E-10BC-4E6C-B280-5CD389E36E4E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{162D6E80-9B72-4A78-8481-39E3AC255C99}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestModel", "basics\Tests\TestModel\TestModel.csproj", "{DA282291-A2AD-428B-8899-C6EC2299C4FF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {12FE8D34-611E-4AB9-93BE-C74A7D947405}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {12FE8D34-611E-4AB9-93BE-C74A7D947405}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -38,11 +45,30 @@ Global {A9FE0291-58A1-42E5-9768-34B3A33F4116}.Debug|Any CPU.Build.0 = Debug|Any CPU {A9FE0291-58A1-42E5-9768-34B3A33F4116}.Release|Any CPU.ActiveCfg = Release|Any CPU {A9FE0291-58A1-42E5-9768-34B3A33F4116}.Release|Any CPU.Build.0 = Release|Any CPU + {12D9ED1E-4A8C-4A67-B56D-16D8AF1287F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {12D9ED1E-4A8C-4A67-B56D-16D8AF1287F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {12D9ED1E-4A8C-4A67-B56D-16D8AF1287F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {12D9ED1E-4A8C-4A67-B56D-16D8AF1287F5}.Release|Any CPU.Build.0 = Release|Any CPU + {E0A3410E-10BC-4E6C-B280-5CD389E36E4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0A3410E-10BC-4E6C-B280-5CD389E36E4E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0A3410E-10BC-4E6C-B280-5CD389E36E4E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0A3410E-10BC-4E6C-B280-5CD389E36E4E}.Release|Any CPU.Build.0 = Release|Any CPU + {DA282291-A2AD-428B-8899-C6EC2299C4FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA282291-A2AD-428B-8899-C6EC2299C4FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA282291-A2AD-428B-8899-C6EC2299C4FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA282291-A2AD-428B-8899-C6EC2299C4FF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {12FE8D34-611E-4AB9-93BE-C74A7D947405} = {98474E06-6ECA-468B-9693-30AA3708FA0A} {93AA2CF9-A29A-41D2-9ECC-87BE7BD121A4} = {98474E06-6ECA-468B-9693-30AA3708FA0A} {991E0C4E-48DE-4699-BCF7-070029923143} = {98474E06-6ECA-468B-9693-30AA3708FA0A} {A9FE0291-58A1-42E5-9768-34B3A33F4116} = {98474E06-6ECA-468B-9693-30AA3708FA0A} + {12D9ED1E-4A8C-4A67-B56D-16D8AF1287F5} = {DFAECDF9-BEF0-4D05-B7C7-238D78986BD0} + {E0A3410E-10BC-4E6C-B280-5CD389E36E4E} = {DFAECDF9-BEF0-4D05-B7C7-238D78986BD0} + {162D6E80-9B72-4A78-8481-39E3AC255C99} = {DFAECDF9-BEF0-4D05-B7C7-238D78986BD0} + {DA282291-A2AD-428B-8899-C6EC2299C4FF} = {162D6E80-9B72-4A78-8481-39E3AC255C99} EndGlobalSection EndGlobal diff --git a/Sources/basics/Model/Book.cs b/Sources/basics/Model/Book.cs new file mode 100644 index 0000000..bf3602c --- /dev/null +++ b/Sources/basics/Model/Book.cs @@ -0,0 +1,70 @@ +namespace Model; + +public class Book : IEquatable +{ + public long Id { get; private init; } + + public string Title + { + get => title; + set => title = value ?? ""; + } + private string title = null!; + + public string Author + { + get => author; + set => author = value ?? ""; + } + private string author = null!; + + public string Isbn + { + get => isbn; + set => isbn = value ?? ""; + } + private string isbn = null!; + + public Person? Borrower { get; set; } + + public Book(long id, string title, string author, string isbn) + { + Id = id; + Title = title; + Author = author; + Isbn = isbn; + } + public Book(string title, string author, string isbn) + : this(0, title, author, isbn) + { + } + + public bool Equals(Book? other) + { + if (Id != 0) + return Id == (other?.Id ?? 0); + else + return Title.Equals(other?.Title) + && Author.Equals(other?.Author) + && Isbn.Equals(other?.Isbn); + } + + public override bool Equals(object? obj) + { + if(ReferenceEquals(obj, null)) return false; + if(ReferenceEquals(obj, this)) return true; + if(GetType() != obj.GetType()) return false; + return Equals(obj as Book); + } + + public override int GetHashCode() + { + if(Id != 0) + return (int)(Id % 7879); + else + return Isbn.GetHashCode(); + } + + public override string ToString() + => $"{Id} - {Title} ({Isbn}) // {Author}"; +} diff --git a/Sources/basics/Model/IDataManager.cs b/Sources/basics/Model/IDataManager.cs new file mode 100644 index 0000000..cd98e46 --- /dev/null +++ b/Sources/basics/Model/IDataManager.cs @@ -0,0 +1,26 @@ +namespace Model; + +public interface IDataManager +{ + Task> GetBooks(int index, int count); + Task> GetBooksByTitle(string title, int index, int count); + Task> GetBooksByAuthor(string author, int index, int count); + Task> GetBooksByIsbn(string isbn, int index, int count); + Task GetBookById(long id); + + Task> GetPersons(int index, int count); + Task> GetPersonsByName(string name, int index, int count); + Task GetPersonById(long id); + Task> GetBooksBorrowedBy(Person person, int index, int count); + + Task CreateBook(string title, string author, string isbn); + Task UpdateBook(long id, Book book); + Task DeleteBook(long id); + + Task CreatePerson(string firstName, string lastName); + Task UpdatePerson(long id, Person person); + Task DeletePerson(long id); + + Task BorrowBook(Book book, Person person); + Task ReturnBook(Book book, Person person); +} diff --git a/Sources/basics/Model/Model.csproj b/Sources/basics/Model/Model.csproj new file mode 100644 index 0000000..bb23fb7 --- /dev/null +++ b/Sources/basics/Model/Model.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/Sources/basics/Model/Person.cs b/Sources/basics/Model/Person.cs new file mode 100644 index 0000000..842899d --- /dev/null +++ b/Sources/basics/Model/Person.cs @@ -0,0 +1,77 @@ +using System.Collections.ObjectModel; +namespace Model; + +public class Person : IEquatable +{ + public long Id { get; private init; } + + public string FirstName + { + get => firstName; + set => firstName = !string.IsNullOrWhiteSpace(value) ? value : "Jane"; + } + private string firstName = null!; + + public string LastName + { + get => lastName; + set => lastName = value ?? ""; + } + private string lastName = null!; + + public ReadOnlyCollection Books { get; private init; } + private List books = new List(); + + public Person(string firstName, string lastName) + : this(0, firstName, lastName) + { } + + public Person(long id, string firstName, string lastName) + { + Id = id; + FirstName = firstName; + LastName = lastName; + Books = new ReadOnlyCollection(books); + } + + public bool BorrowBook(Book book) + { + if(books.Contains(book)) + return false; + books.Add(book); + return true; + } + + public bool ReturnBook(Book book) + { + return books.Remove(book); + } + + public bool Equals(Person? other) + { + if(Id != 0) + return Id == (other?.Id ?? 0); + else + return FirstName.Equals(other?.FirstName) + && LastName.Equals(other?.LastName); + } + + public override bool Equals(object? obj) + { + if(ReferenceEquals(obj, null)) return false; + if(ReferenceEquals(this, obj)) return true; + if(GetType() != obj?.GetType()) return false; + return Equals(obj as Person); + } + + public override int GetHashCode() + { + if(Id != 0) + return (int)(Id % 7879); + else + return FirstName.GetHashCode() + LastName.GetHashCode(); + } + + public override string ToString() + => $"{Id} - {FirstName} {LastName} (#books: {Books.Count})"; +} diff --git a/Sources/basics/Stub/Stub.csproj b/Sources/basics/Stub/Stub.csproj new file mode 100644 index 0000000..e9f1625 --- /dev/null +++ b/Sources/basics/Stub/Stub.csproj @@ -0,0 +1,13 @@ + + + + + + + + net8.0 + enable + enable + + + diff --git a/Sources/basics/Stub/StubDataManager.cs b/Sources/basics/Stub/StubDataManager.cs new file mode 100644 index 0000000..bfad991 --- /dev/null +++ b/Sources/basics/Stub/StubDataManager.cs @@ -0,0 +1,225 @@ +using Model; + +namespace Stub; + +public class StubDataManager : IDataManager +{ + private readonly static List books = new() + { + new Book(1, "Les Trois Mousquetaires", "Alexandre Dumas", "979-8415441792"), + new Book(2, "Vingt Ans Après", "Alexandre Dumas", "979-8805372019"), + new Book(3, "Le Vicomte de Bragelonne - Livre Premier", "Alexandre Dumas", "979-8376127575"), + new Book(4, "Le Vicomte de Bragelonne - Livre Second", "Alexandre Dumas", "979-8376430019"), + new Book(5, "Vingt mille lieues sous les Mers", "Jules Verne", "979-8489937313"), + new Book(6, "L'île mystérieuse", "Jules Verne", "979-8492533861"), + new Book(7, "Les enfants du capitaine Grant", "Jules Verne", "979-8757717418"), + new Book(8, "Les Misérables", "Victor Hugo", "979-8850172916"), + new Book(9, "Notre-Dame de Paris", "Victor Hugo", "979-8863757285"), + new Book(10, "Quatrevingt-treize", "Victor Hugo", "979-8863954202"), + new Book(11, "Han d'Islande", "Victor Hugo", "979-8864251553"), + }; + + private readonly static List persons = new() + { + new Person(1, "Chick", "Corea"), + new Person(2, "Stanley", "Clarke"), + new Person(3, "Lenny", "White"), + new Person(4, "Frank", "Gambale"), + new Person(5, "Jean-Luc", "Ponty"), + new Person(6, "Al", "Di Meola"), + new Person(7, "Steve", "Gadd"), + }; + + private long bookId = 12; + + private long personId = 8; + + static StubDataManager() + { + books[0].Borrower = persons[0]; + persons[0].BorrowBook(books[0]); + + books[1].Borrower = persons[0]; + persons[0].BorrowBook(books[1]); + + books[2].Borrower = persons[0]; + persons[0].BorrowBook(books[2]); + + books[3].Borrower = persons[0]; + persons[0].BorrowBook(books[3]); + + books[4].Borrower = persons[1]; + persons[1].BorrowBook(books[4]); + + books[5].Borrower = persons[1]; + persons[1].BorrowBook(books[5]); + + books[6].Borrower = persons[1]; + persons[1].BorrowBook(books[6]); + + books[8].Borrower = persons[2]; + persons[2].BorrowBook(books[8]); + + books[10].Borrower = persons[3]; + persons[3].BorrowBook(books[10]); + } + + public Task BorrowBook(Book book, Person person) + { + var foundBook = GetBookById(book.Id).Result; + var foundPerson = GetPersonById(person.Id).Result; + + if(foundBook == null || foundPerson == null) + return Task.FromResult(false); + + if(foundBook.Borrower != null) + { + return Task.FromResult(false); + } + foundBook.Borrower = foundPerson; + foundPerson.BorrowBook(foundBook); + return Task.FromResult(true); + } + + public Task CreateBook(string title, string author, string isbn) + { + Book book = new Book(bookId, title, author, isbn); + books.Add(book); + bookId++; + return Task.FromResult(book); + } + + public Task CreatePerson(string firstName, string lastName) + { + Person person = new Person(personId, firstName, lastName); + persons.Add(person); + personId++; + return Task.FromResult(person); + } + + public Task DeleteBook(long id) + { + Book? book = books.SingleOrDefault(b => b.Id == id); + if(book == null) + { + return Task.FromResult(false); + } + book.Borrower?.ReturnBook(book); + books.Remove(book); + return Task.FromResult(true); + } + + public Task DeletePerson(long id) + { + Person? person = persons.SingleOrDefault(p => p.Id == id); + if(person == null) + { + return Task.FromResult(false); + } + foreach(var book in person.Books) + { + book.Borrower = null; + } + persons.Remove(person); + return Task.FromResult(true); + } + + public Task GetBookById(long id) + { + return Task.FromResult(books.SingleOrDefault(b => b.Id == id)); + } + + public Task> GetBooks(int index, int count) + { + var foundBooks = books.Skip(index*count).Take(count); + return Task.FromResult(foundBooks); + } + + public Task> GetBooksBorrowedBy(Person person, int index, int count) + { + var foundBooks = books.Where(b => person.Equals(b.Borrower)) + .Skip(index*count).Take(count); + return Task.FromResult(foundBooks); + } + + public Task> GetBooksByAuthor(string author, int index, int count) + { + var foundBooks = books.Where(b => b.Author.Contains(author, StringComparison.InvariantCultureIgnoreCase)) + .Skip(index*count).Take(count); + return Task.FromResult(foundBooks); + } + + public Task> GetBooksByIsbn(string isbn, int index, int count) + { + var foundBooks = books.Where(b => b.Isbn.Equals(isbn)) + .Skip(index*count).Take(count); + return Task.FromResult(foundBooks); + } + + public Task> GetBooksByTitle(string title, int index, int count) + { + var foundBooks = books.Where(b => b.Title.Contains(title, StringComparison.InvariantCultureIgnoreCase)) + .Skip(index*count).Take(count); + return Task.FromResult(foundBooks); + } + + public Task GetPersonById(long id) + { + return Task.FromResult(persons.SingleOrDefault(p => p.Id == id)); + } + + public Task> GetPersons(int index, int count) + { + var foundPersons = persons.Skip(index*count).Take(count); + return Task.FromResult(foundPersons); + } + + public Task> GetPersonsByName(string name, int index, int count) + { + var foundPersons = persons.Where(b => b.FirstName.Contains(name, StringComparison.InvariantCultureIgnoreCase) + || b.LastName.Contains(name, StringComparison.InvariantCultureIgnoreCase)) + .Skip(index*count).Take(count); + return Task.FromResult(foundPersons); + } + + public Task ReturnBook(Book book, Person person) + { + var foundBook = GetBookById(book.Id).Result; + var foundPerson = GetPersonById(person.Id).Result; + + if(foundBook == null || foundPerson == null) + return Task.FromResult(false); + + if(!foundPerson.Books.Contains(foundBook) || !foundPerson.Equals(foundBook.Borrower)) + return Task.FromResult(false); + + foundPerson.ReturnBook(foundBook); + foundBook.Borrower = null; + return Task.FromResult(true); + } + + public Task UpdateBook(long id, Book book) + { + var foundBook = GetBookById(id).Result; + + if(foundBook == null) + return Task.FromResult((Book?)null); + + foundBook.Author = book.Author; + foundBook.Isbn = book.Isbn; + foundBook.Title = book.Title; + return Task.FromResult((Book?)foundBook); + } + + public Task UpdatePerson(long id, Person person) + { + var foundPerson = GetPersonById(id).Result; + + if(foundPerson == null) + return Task.FromResult((Person?)null); + + foundPerson.FirstName = person.FirstName; + foundPerson.LastName = person.LastName; + return Task.FromResult((Person?)foundPerson); + } +} diff --git a/Sources/basics/Tests/TestModel/PersonTest.cs b/Sources/basics/Tests/TestModel/PersonTest.cs new file mode 100644 index 0000000..7d3cd1d --- /dev/null +++ b/Sources/basics/Tests/TestModel/PersonTest.cs @@ -0,0 +1,46 @@ +using Model; + +namespace TestModel; + +public class PersonTest +{ + [Fact] + public void TestFirstName_CorrectValue() + { + Person person = new Person("Chuck", "McGill"); + Assert.Equal("Chuck", person.FirstName); + + person.FirstName = "Jimmy"; + Assert.Equal("Jimmy", person.FirstName); + } + + [Fact] + public void TestFirstName_NullValue() + { + Person person = new Person("Jimmy", "McGill"); + Assert.Equal("Jimmy", person.FirstName); + + person.FirstName = null; + Assert.Equal("Jane", person.FirstName); + } + + [Fact] + public void TestFirstName_EmptyValue() + { + Person person = new Person("Jimmy", "McGill"); + Assert.Equal("Jimmy", person.FirstName); + + person.FirstName = ""; + Assert.Equal("Jane", person.FirstName); + } + + [Fact] + public void TestFirstName_WhiteSpacesValue() + { + Person person = new Person("Jimmy", "McGill"); + Assert.Equal("Jimmy", person.FirstName); + + person.FirstName = " "; + Assert.Equal("Jane", person.FirstName); + } +} \ No newline at end of file diff --git a/Sources/basics/Tests/TestModel/TestModel.csproj b/Sources/basics/Tests/TestModel/TestModel.csproj new file mode 100644 index 0000000..be2c62c --- /dev/null +++ b/Sources/basics/Tests/TestModel/TestModel.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + +