diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index fd9cd3e..f251c36 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -2,7 +2,7 @@ name: Deploy backend to staging on: push: - branches: ['development', 'gh-actions', 's23-docker-compose-tests'] + branches: ['development', 'gh-actions', s23-docker-compose-tests] env: REGISTRY: ghcr.io diff --git a/Dockerfile.staging b/Dockerfile.staging index 30eff6e..c44ab5e 100644 --- a/Dockerfile.staging +++ b/Dockerfile.staging @@ -1,5 +1,5 @@ # Stage 1: Build -FROM node:alpine as builder +FROM node:16-bullseye as builder WORKDIR /app @@ -14,10 +14,12 @@ COPY . . RUN npm run build # Stage 2: Run -FROM node:alpine +FROM node:16-bullseye WORKDIR /app +# Copy SQL scripts for database initialization MAYBE NOT NEEDED in production/staging +COPY sql /app/sql # Copy built assets from builder stage COPY --from=builder /app/build ./build # Only copy the necessary files for runtime diff --git a/docker-compose-staging.yml b/docker-compose-staging.yml index 775976e..47634b2 100644 --- a/docker-compose-staging.yml +++ b/docker-compose-staging.yml @@ -6,6 +6,6 @@ services: image: ${IMAGE_NAME} build: context: . - dockerfile: Dockerfile + dockerfile: Dockerfile.staging ports: - 3002:3002 diff --git a/robot/common.resource b/robot/common.resource index 0cda399..1da7704 100644 --- a/robot/common.resource +++ b/robot/common.resource @@ -8,3 +8,5 @@ ${BACKENDTESTPASSWORD} default # this will be set in initial login test ${bearerToken} default +${userId} default + diff --git a/robot/tests/01_loginTests.robot b/robot/tests/01_loginTests.robot index 1c62f80..3da598b 100644 --- a/robot/tests/01_loginTests.robot +++ b/robot/tests/01_loginTests.robot @@ -33,4 +33,6 @@ Verify user can login ${response}= POST ${URL}/auth/login json=${data} expected_status=200 Should Be True ${response.json()['ok']} Set Global Variable ${bearerToken} access_token=${response.json()['secret']} + Set Global Variable ${userId} ${response.json()['userId']} Log To Console ${bearerToken} + Log To Console ${userId} diff --git a/robot/tests/04_bookTests.robot b/robot/tests/04_bookTests.robot index 07128ad..dbfe3da 100644 --- a/robot/tests/04_bookTests.robot +++ b/robot/tests/04_bookTests.robot @@ -36,16 +36,6 @@ Verify that a nonexistent book can't be deleted ${response}= DELETE url=${URL}/book/?id=12321&${bearerToken} expected_status=403 Should Not Be True ${response.json()['ok']} -Verify user can check all borrowed books - ${response}= GET url=${URL}/borrow/all?${bearerToken} expected_status=200 - -Verify that user cannot get a nonexisting borrowed book - ${response}= GET url=${URL}/borrow/?id=123456789&${bearerToken} expected_status=200 - Should Be Equal ${response.json()} ${None} - -Verify that user can check borrowed book by id - ${response}= GET url=${URL}/borrow/?id=1&${bearerToken} expected_status=200 - Should Be Equal ${response.json()['id']} ${1} # Verify that book can be updated # &{data}= Create dictionary { library_user=2 title=Fake asdf image=http://books.google.com/books/content?id=ILqrxQEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api author=Test Author year=2022 isbn=123-242-421 topic=js location=Fake location deleted=0} diff --git a/robot/tests/05_borrowTests.robot b/robot/tests/05_borrowTests.robot new file mode 100644 index 0000000..47c8703 --- /dev/null +++ b/robot/tests/05_borrowTests.robot @@ -0,0 +1,32 @@ +*** Settings *** +Resource ../common.resource +Library RequestsLibrary +Library String + +*** Test Cases *** +Verify that all borrows can be found + ${response}= GET url=${URL}/borrow/all?${bearerToken} expected_status=200 + +Verify that user can find borrow by id + ${response}= GET url=${URL}/borrow/?id=1&${bearerToken} expected_status=200 + Should Be Equal ${response.json()['id']} ${1} + +Verify that user can't find borrow by non-existing id + ${response}= GET url=${URL}/borrow/?id=12345&${bearerToken} expected_status=200 + Should Be Equal ${response.json()} ${none} + +Verify that user can check borrowed book by id + ${response}= GET url=${URL}/borrow/?id=1&${bearerToken} expected_status=200 + Should Be Equal ${response.json()['id']} ${1} + +Verify that user can't delete non-existing borrow + &{data}= Create dictionary + ... { bookId=1234 } + ${response}= DELETE url=${URL}/borrow?${bearerToken} json=${data} expected_status=403 + +Verify that user can get current borrows + ${response}= GET url=${URL}/borrow/current?${bearerToken} expected_status=200 + +Verify that user can get borrows by session + ${response}= GET url=${URL}/borrow/session?${bearerToken} expected_status=200 + diff --git a/robot/tests/06_booklistTests.robot b/robot/tests/06_booklistTests.robot new file mode 100644 index 0000000..12cc40e --- /dev/null +++ b/robot/tests/06_booklistTests.robot @@ -0,0 +1,82 @@ +*** Settings *** +Resource ../common.resource +Library RequestsLibrary +Library String + +*** Variables *** +${okTrueJson} {"ok":true} +${okFalseJson} {"ok":false} + +*** Test Cases *** +Verify that user can add booklists + ${data}= Create dictionary + ... name=Testlist + ${response}= POST url=${URL}/booklist?${bearerToken} json=${data} expected_status=200 + +Verify that user can check all booklists + ${response}= GET url=${URL}/booklist/all?${bearerToken} expected_status=200 + +Verify that user can check all users booklists + ${response}= GET url=${URL}/booklist/user?${bearerToken} expected_status=200 + ${bookList}= Evaluate json.loads('''${response.text}''') + Length Should Be ${bookList} 1 + +Verify that user can add books to list + ${data}= Create dictionary + ... list=1 + ... book=2 + ${response}= POST url=${URL}/booklistentry?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can't add non-existing books to list + ${data}= Create dictionary + ... list=1 + ... book=2123 + ${response}= POST url=${URL}/booklistentry?${bearerToken} json=${data} expected_status=500 + Should Be Equal As Strings ${response.text} ${okFalseJson} + +Verify that user can't add books to non-existing list + ${data}= Create dictionary + ... list=11234 + ... book=2 + ${response}= POST url=${URL}/booklistentry?${bearerToken} json=${data} expected_status=500 + Should Be Equal As Strings ${response.text} ${okFalseJson} + +Verify that user can get all booklistentrys + ${response}= GET url=${URL}/booklistentry/all?${bearerToken} expected_status=200 + +Verify that user get all booklistentrys based on listId + ${response}= GET url=${URL}/booklistentry/list?${bearerToken}&id=1 expected_status=200 + ${bookList}= Evaluate json.loads('''${response.text}''') + Length Should Be ${bookList} 1 + +Verify that user get all books in list based on listId + ${response}= GET url=${URL}/booklist/books?${bearerToken}&id=1 expected_status=200 + ${bookList}= Evaluate json.loads('''${response.text}''') + Length Should Be ${bookList} 1 + +Verify that user can modify booklist + ${data}= Create dictionary + ... id=1 + ... name=New name + ${response}= PUT url=${URL}/booklist?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can't modify non-existing booklist + ${data}= Create dictionary + ... id=12345 + ... name=Wrong list + ${response}= PUT url=${URL}/booklist?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okFalseJson} + +Verify that user can delete booklist + ${data}= Create dictionary + ... id=1 + ${response}= DELETE url=${URL}/booklist?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can't delete non-existing booklist + ${data}= Create dictionary + ... id=1123 + ${response}= DELETE url=${URL}/booklist?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okFalseJson} \ No newline at end of file diff --git a/robot/tests/07_bookFavoriteTests.robot b/robot/tests/07_bookFavoriteTests.robot new file mode 100644 index 0000000..af98f9b --- /dev/null +++ b/robot/tests/07_bookFavoriteTests.robot @@ -0,0 +1,34 @@ +*** Settings *** +Resource ../common.resource +Library RequestsLibrary +Library String + +*** Test Cases *** +Verify that user can add favorite + &{data}= Create dictionary + ... bookId=4 + ${response}= POST url=${URL}/favorite?${bearerToken} json=${data} expected_status=200 + Should Be True ${response.json()['ok']} + +Verify that user can get counts for all favorited books + ${response}= GET url=${URL}/favorite/counts?${bearerToken} expected_status=200 + +Verify that user can get count for books + ${response}= GET url=${URL}/favorite/count?${bearerToken}&bookId=4 expected_status=200 + Should Be Equal ${response.json()['count']} ${1} + +Verify that user can check if book is favorited + ${response}= GET url=${URL}/favorite/check?${bearerToken}&bookId=4 expected_status=200 + Should Be Equal ${response.json()['isFavorited']} ${true} + +Verify that user can delete book from favorites + &{data}= Create dictionary + ... bookId=4 + ${response}= DELETE url=${URL}/favorite?${bearerToken} json=${data} expected_status=200 + Should Be True ${response.json()['ok']} + +Verify that user can't delete book that isn't favorited + &{data}= Create dictionary + ... bookId=3123 + ${response}= DELETE url=${URL}/favorite?${bearerToken} expected_status=400 + Should Not Be True ${response.json()['ok']} diff --git a/robot/tests/08_bookReviewTests.robot b/robot/tests/08_bookReviewTests.robot new file mode 100644 index 0000000..86b04a7 --- /dev/null +++ b/robot/tests/08_bookReviewTests.robot @@ -0,0 +1,48 @@ +*** Settings *** +Resource ../common.resource +Library RequestsLibrary +Library String + +*** Variables *** +${okTrueJson} {"ok":true} +${okFalseJson} {"ok":false} + + +*** Test Cases *** +Verify that user can add review to book + ${data}= Create dictionary + ... bookId=1 + ... comment='Great book for everyone!' + ... rating=5 + ${response}= POST url=${URL}/review?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can't add review to non-existing book + ${data}= Create dictionary + ... bookId=1124 + ... comment='Wrong book' + ... rating=1 + ${response}= POST url=${URL}/review?${bearerToken} json=${data} expected_status=500 + Should Be Equal As Strings ${response.text} ${okFalseJson} + +Verify that user can check all reviews + ${response}= GET url=${URL}/review/all?${bearerToken} expected_status=200 + +Verify that user can check reviews for all books + ${response}= GET url=${URL}/review/reviews?${bearerToken} expected_status=200 + +Verify that user can modify review + ${data}= Create dictionary + ... comment='Modified book' + ... rating=1 + ... reviewId=1 + ${response}= PUT url=${URL}/review?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can't modify non-existing review + ${data}= Create dictionary + ... comment='Again wrong book' + ... rating=1 + ... reviewId=1234 + ${response}= PUT url=${URL}/review?${bearerToken} json=${data} expected_status=400 + Should Not Be True ${response.json()['ok']} \ No newline at end of file diff --git a/robot/tests/09_bookRequestTests.robot b/robot/tests/09_bookRequestTests.robot new file mode 100644 index 0000000..d1a9027 --- /dev/null +++ b/robot/tests/09_bookRequestTests.robot @@ -0,0 +1,18 @@ +*** Settings *** +Resource ../common.resource +Library RequestsLibrary +Library String + + +*** Test Cases *** +Verify that user can add request + ${data}= Create dictionary + ... isbn=234-123-345 + ... title=Lizard Disco + ... reason=Great book, easy to read + ${response}= POST url=${URL}/bookrequest?${bearerToken} json=${data} expected_status=200 + +Verify that user can check all users booklists + ${response}= GET url=${URL}/bookrequest/all?${bearerToken} expected_status=200 + ${list}= Evaluate json.loads('''${response.text}''') + Length Should Be ${list} 1 \ No newline at end of file diff --git a/robot/tests/10_bookReservationTests.robot b/robot/tests/10_bookReservationTests.robot new file mode 100644 index 0000000..d6b9f2a --- /dev/null +++ b/robot/tests/10_bookReservationTests.robot @@ -0,0 +1,67 @@ +*** Settings *** +Resource ../common.resource +Library RequestsLibrary +Library String + +*** Variables *** +${okTrueJson} {"ok":true} +${okFalseJson} {"ok":false} + +*** Test Cases *** +Verify that user can add reservations + ${data}= Create dictionary + ... bookId=2 + ${response}= POST url=${URL}/bookreservation?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can't add reservations for non-existing book + ${data}= Create dictionary + ... bookId=1123 + ${response}= POST url=${URL}/bookreservation?${bearerToken} json=${data} expected_status=500 + Should Be Equal As Strings ${response.text} ${okFalseJson} + +Verify that user can check all reservations + ${response}= GET url=${URL}/bookreservation/all?${bearerToken} expected_status=200 + ${list}= Evaluate json.loads('''${response.text}''') + Length Should Be ${list} 1 + +Verify that user can check all extended reservations + ${response}= GET url=${URL}/bookreservation/all/extended?${bearerToken} expected_status=200 + ${list}= Evaluate json.loads('''${response.text}''') + Length Should Be ${list} 1 + +Verify that user can check reservation based on bookId + ${data}= Create dictionary + ... bookId=2 + ${response}= GET url=${URL}/bookreservation/book?${bearerToken} json=${data} expected_status=200 + Should Be Equal ${response.json()['bookId']} ${2} + +Verify that user can't check reservation based on non-existing bookId + ${data}= Create dictionary + ... bookId=33245 + ${response}= GET url=${URL}/bookreservation/book?${bearerToken} json=${data} expected_status=200 + Should Be Equal ${response.json()} ${none} + +Verify that user can cancel reservation + ${data}= Create dictionary + ... reservationId=1 + ${response}= POST url=${URL}/bookreservation/cancel?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can check all current reservations + ${response}= GET url=${URL}/bookreservation/all/current?${bearerToken} expected_status=200 + ${list}= Evaluate json.loads('''${response.text}''') + Length Should Be ${list} 0 + +Verify that user can loan reservation + ${data}= Create dictionary + ... reservationId=1 + ${response}= POST url=${URL}/bookreservation/loan?${bearerToken} json=${data} expected_status=200 + Should Be Equal As Strings ${response.text} ${okTrueJson} + +Verify that user can see their current reservations + ${data}= Create dictionary + ... userId=${userId} + ${response}= POST url=${URL}/bookreservation/user/current?${bearerToken} json=${data} expected_status=200 + ${list}= Evaluate json.loads('''${response.text}''') + Should Be Equal ${response.json()} ${none} diff --git a/robot/tests/05_logoutTests.robot b/robot/tests/11_logoutTests.robot similarity index 100% rename from robot/tests/05_logoutTests.robot rename to robot/tests/11_logoutTests.robot diff --git a/sql/01_EfiLibrary.sql b/sql/01_EfiLibrary.sql index d16d243..aacb26d 100644 --- a/sql/01_EfiLibrary.sql +++ b/sql/01_EfiLibrary.sql @@ -72,12 +72,17 @@ CREATE TABLE IF NOT EXISTS `book` ( ) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1; -- Dumping data for table efilibrarydb.book: ~3 rows (approximately) -INSERT INTO `book` (`id`, `library_user`, `title`, `image`,`author`, `year`, `isbn`, `topic`, `location`) VALUES - (1, 1, 'JS for Dummies', "https://images.isbndb.com/covers/91/26/9789513119126.jpg", 'Mikko Mallikas', 2000, '123-456-789', 'JS', 'Nevada'), - (2, 2, 'Java for Babies', "https://images.isbndb.com/covers/91/26/9789513119126.jpg", 'John Doe', 2015, '123-5223-789', 'Java', 'Florida'), - (3, 3, 'Python for Pets', "https://images.isbndb.com/covers/91/26/9789513119126.jpg", 'S. Bonsai', 2020, '123-456-5623', 'JS', 'Hong Kong'), - (4, 5, 'Assembly for Infants', "https://images.isbndb.com/covers/91/26/9789513119126.jpg", 'A. Einstein', 2007, '999-999-999', 'Assembly', 'Switzerland'), - (5, 5, 'Animal Pictures for Programmers', "https://images.isbndb.com/covers/91/26/9789513119126.jpg", 'Oreally', 2010, '987-654-321', 'Safari', 'Pacific Ocean'); +INSERT INTO `book` (`id`, `library_user`, `title`, `image`,`author`, `year`, `isbn`, `topic`, `location`, `language`, `description`) VALUES + (1, 1, 'JavaScript All-in-One For Dummies', "http://books.google.com/books/content?id=Co61EAAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api", 'Chris Minnick', 2023, '9781119906834', 'JavaScript', 'Nevada', 'en', + "A developer’s resource to learning one of the most-used scripting languages JavaScript All-in-One For Dummies saves you shelf space by offering a complete introduction to JavaScript and how it’s used in the real world. This book serves up JavaScript coding basics before diving into the tools, libraries, frameworks, and runtime environments new and experienced coders need to know. Start by learning the basics of JavaScript and progress through the techniques and tools used by professional JavaScript developers, even if you’ve never written code before. You also get the details of today’s hottest libraries and frameworks—React.js, Vue.js, Svelte, and Node.js. Learn the basics of web and application development with the JavaScript language Grasp the similarities and differences between React.js, Vue.js, and Svelte Discover how to write server-side JavaScript and how to access databases with Node.js Gain a highly marketable skill, with one of the most popular coding languages Launch or further your career as a coder with easy-to-follow instruction This is the go-to Dummies guide for future and current coders who need an all-inclusive guide JavaScript. This is the go-to Dummies guide for future and current coders who need an all-inclusive guide to the world of JavaScript."), + (2, 2, 'Pieni Java 8 -kirja', "http://books.google.com/books/content?id=bEWCBAAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api", 'Juha Peltomäki', 2014, '9789522869968', 'Java', 'Florida', 'fi', + "Uuden suomenkielisen Java-perusteoksen toinen painos! Kirja sopii opiskelijalle, Javaa aloittavalle sekä Javan aiempia versioita hieman osaavalle. Tiivis kirja on sopiva sekä oppilaitosten kurssikirjaksi että itseopiskeluun. Java on säilyttänyt jo lähes 20 vuotta asemansa johtavana olio-ohjelmointikielenä. Java 8 tuo mukanaan kaivatun tuen funktionaaliselle ohjelmoinnille lambda-lausekkeiden muodossa. Kirjan alkuosassa käydään läpi Javan perusteet, olio-ohjelmointi ja kokoelmat. Loppuosa pureutuu Java 8:n tärkeimpiin uudistuksiin eli lambda-lausekkeisiin ja uuteen graafiseen JavaFX-kirjastoon. Kirjaan liittyvän lisämateriaalin, kuten esimerkit, voi ladata osoitteesta books.jupelearning.com."), + (3, 3, 'Python for Kids, 2nd Edition', "http://books.google.com/books/content?id=R511EAAAQBAJ&printsec=frontcover&img=1&zoom=1&edge=curl&source=gbs_api", 'Jason R. Briggs', 2022, '9781718503021', 'Python', 'Hong Kong', 'en', + "The second edition of the best-selling Python for Kids—which brings you (and your parents) into the world of programming—has been completely updated to use the latest version of Python, along with tons of new projects! Python is a powerful, expressive programming language that’s easy to learn and fun to use! But books about learning to program in Python can be dull and gray—and that’s no fun for anyone. Python for Kids brings Python to life and brings kids (and their parents) into the wonderful world of programming. Author Jason R. Briggs guides readers through the basics, experimenting with unique (and often hilarious) example programs that feature ravenous monsters, secret agents, thieving ravens, and more. New terms are defined; code is colored, dissected, and explained; and quirky, full-color illustrations keep things fun and engaging throughout. Chapters end with programming puzzles designed to stretch the brain and strengthen understanding. By the end of the book, young readers will have programmed two complete games: a clone of the famous Pong, and “Mr. Stick Man Races for the Exit”—a platform game with jumps, animation, and much more. This second edition has been completely updated and revised to reflect the latest Python version and programming practices, with new puzzles to inspire readers to take their code farther than ever before. Why should serious adults have all the fun? Python for Kids is the ticket into the amazing world of computer programming."), + (4, 5, 'Docker Cookbook', "http://books.google.com/books/content?id=VcjYrQEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api", 'Sébastien Goasguen', 2015, '9781491919712', 'Docker', 'Switzerland', 'en', + "Whether you’re deploying applications on-premise or in the cloud, this cookbook is for developers, operators, and IT professionals who need practical solutions for using Docker. The recipes in this book will help developers go from zero knowledge to distributed applications packaged and deployed within a couple of chapters. IT professionals will be able to use this cookbook to solve everyday problems, as well as create, run, share, and deploy Docker images quickly. Operators will learn and understand what developers are excited about and start to adopt the tools that will change the way they work. Get started using Docker immediately Learn how to use Docker for large-scale deployments Become familiar with other tools in the Docker ecosystem, such as kubernetes and coreos Use Docker in production, with recipes on cloud administration, monitoring, and common related components Docker’s new approach to containerization and its enhancements in terms of security, integration with existing development tools, and application sharing are revolutionizing the way developers think about creating new applications and the way system administrators might operate their infrastructure in the future."), + (5, 5, 'Learn C# in One Day and Learn It Well', "http://books.google.com/books/content?id=n_YZjwEACAAJ&printsec=frontcover&img=1&zoom=1&source=gbs_api", 'Jamie Chan', 2015, '9781518800276', 'C#', 'Pacific Ocean', 'en', + "Have you ever wanted to learn computer programming but were afraid it would be too difficult for you? Or perhaps you already know other programming languages, and are now interested in learning C#. C# is part of the .Net framework and is intended to be a simple general-purpose programming language that can be used to develop different types of applications, including console, windows, web and mobile apps."); -- Dumping structure for table efilibrarydb.borrowing DROP TABLE IF EXISTS `borrowing`; diff --git a/src/queries/__mocks__/book.ts b/src/queries/__mocks__/book.ts index ca603c9..5dfba83 100644 --- a/src/queries/__mocks__/book.ts +++ b/src/queries/__mocks__/book.ts @@ -70,7 +70,7 @@ let book4: Book = { deleted: false, } -let mockBookData = [book1, book2, book3, book4] +export let mockBookData = [book1, book2, book3, book4] let idCounter = 4 const getBook = (id: number) => { @@ -82,11 +82,11 @@ const getBook = (id: number) => { return null } -export const querySelectBook = async (bookId: number): Promise => { +export const getBookById = async (bookId: number): Promise => { return getBook(bookId) } -export const querySelectAllBooks = async (): Promise => { +export const getAllExistingBooks = async (): Promise => { let array: Array = [] mockBookData.forEach((element) => { if (!element.deleted) { @@ -95,11 +95,19 @@ export const querySelectAllBooks = async (): Promise => { }) return array as Array } -export const querySelectAllExistingBooks = async (): Promise => { +export const getAllBooks = async (): Promise => { return mockBookData as Array } -export const queryHardDeleteBook = async (bookId: number): Promise => { +export const getCountOfAllBooks = async (): Promise => { + let count = 0 + for (let index = 0; index < mockBookData.length; index++) { + count++ + } + return count +} + +export const deleteBook = async (bookId: number): Promise => { let deleted = false mockBookData = mockBookData.filter((book) => { if (book.id == bookId) deleted = true @@ -108,7 +116,7 @@ export const queryHardDeleteBook = async (bookId: number): Promise => { return deleted } -export const querySoftDeleteBook = async (bookId: number): Promise => { +export const markBookAsDeleted = async (bookId: number): Promise => { const book = getBook(bookId) if (book) { book.deleted = true @@ -117,7 +125,7 @@ export const querySoftDeleteBook = async (bookId: number): Promise => { return false } -export const queryInsertBook = async ( +export const insertNewBook = async ( userId: number, title: string, image: string, @@ -150,7 +158,7 @@ export const queryInsertBook = async ( return true } -export const queryUpdateBook = async (book: Book): Promise => { +export const updateBook = async (book: Book): Promise => { const editedBook = getBook(book.id) if (editedBook) { editedBook.title = book.title diff --git a/src/queries/__mocks__/book_favorite.ts b/src/queries/__mocks__/book_favorite.ts new file mode 100644 index 0000000..60f1d56 --- /dev/null +++ b/src/queries/__mocks__/book_favorite.ts @@ -0,0 +1,99 @@ +import Book_favorite from '../../interfaces/book_favorite.interface' + +// note: this mock is very basic and its purpose is for jest to run through routes + +let date = new Date() + +let favoritebook1: Book_favorite = { + id: 1, + userId: 1, + bookId: 1, + favoritedDatetime: date, +} + +let favoritebook2: Book_favorite = { + id: 2, + userId: 1, + bookId: 2, + favoritedDatetime: date, +} + +let favoritebook3: Book_favorite = { + id: 3, + userId: 2, + bookId: 1, + favoritedDatetime: date, +} + +let mockFavoriteBookData = [favoritebook1, favoritebook2, favoritebook3] +let sessionId = 1 + +const getBook = (userId: number, bookId: number) => { + for (let index = 0; index < mockFavoriteBookData.length; index++) { + if ( + mockFavoriteBookData[index].userId === userId && + mockFavoriteBookData[index].bookId === bookId + ) { + return mockFavoriteBookData[index] + } + } + return null +} + +export const isBookFavoritedByUser = async ( + userId: number, + bookId: number +): Promise => { + let book = getBook(userId, bookId) + if (book !== null) { + return true + } + return false +} + +export const getFavoriteCountForBook = async ( + bookId: number +): Promise => { + let favoriteCount: number = 0 + for (let index = 0; index < mockFavoriteBookData.length; index++) { + if (mockFavoriteBookData[index].bookId === bookId) { + favoriteCount = favoriteCount + 1 + } + } + return favoriteCount +} + +export const addFavoriteBook = async ( + userId: number, + bookId: number +): Promise => { + let lengthBefore = mockFavoriteBookData.length + mockFavoriteBookData.push({ + id: mockFavoriteBookData.length + 1, + userId: userId, + bookId: bookId, + favoritedDatetime: new Date(), + }) + if (mockFavoriteBookData.length > lengthBefore) { + return true + } + return false +} + +export const deleteFavoriteBook = async ( + userId: number, + bookId: number +): Promise => { + let lengthBefore = mockFavoriteBookData.length + let array: Array = [] + mockFavoriteBookData.forEach((element) => { + if (element.userId !== userId && element.bookId !== bookId) { + array.push(element) + } + }) + mockFavoriteBookData = array + if (lengthBefore > array.length) { + return true + } + return false +} diff --git a/src/queries/__mocks__/book_list.ts b/src/queries/__mocks__/book_list.ts index d9cf758..8e03c66 100644 --- a/src/queries/__mocks__/book_list.ts +++ b/src/queries/__mocks__/book_list.ts @@ -32,11 +32,11 @@ const getBook_list = (id: number) => { return null } -export const querySelectAllLists = async () => { +export const getAllLists = async () => { return mockBook_listData as Array } -export const querySelectListByUser = async (userId: number) => { +export const getListsByUser = async (userId: number) => { let array: Array = [] mockBook_listData.forEach((element) => { if (element.user == userId) { @@ -46,11 +46,11 @@ export const querySelectListByUser = async (userId: number) => { return array as Array } -export const querySelectList = async (listId: number) => { +export const getListById = async (listId: number) => { return getBook_list(listId) } -export const queryInsertNewList = async ( +export const insertNewList = async ( userId: number, listName: string ): Promise => { @@ -62,7 +62,7 @@ export const queryInsertNewList = async ( return true } -export const queryDeleteList = async (listId: number) => { +export const deleteList = async (listId: number) => { let deleted = false mockBook_listData = mockBook_listData.filter((book_list) => { if (book_list.id == listId) deleted = true @@ -71,7 +71,7 @@ export const queryDeleteList = async (listId: number) => { return deleted } -export const queryUpdateList = async (book_list: Book_list) => { +export const updateList = async (book_list: Book_list) => { const editedBook_list = getBook_list(book_list.id) if (editedBook_list) { editedBook_list.name = book_list.name diff --git a/src/queries/__mocks__/book_list_entry.ts b/src/queries/__mocks__/book_list_entry.ts index 5996f17..e71f9e9 100644 --- a/src/queries/__mocks__/book_list_entry.ts +++ b/src/queries/__mocks__/book_list_entry.ts @@ -36,11 +36,11 @@ const getBook_list_entry = (id: number) => { return null } -export const querySelectAllEntries = async () => { +export const getAllEntries = async () => { return mockBook_list_entryData as Array } -export const querySelectAllEntriesByList = async (listId: number) => { +export const getEntriesByList = async (listId: number) => { let array: Array = [] mockBook_list_entryData.forEach((element) => { if (element.list == listId) array.push(element) @@ -48,11 +48,11 @@ export const querySelectAllEntriesByList = async (listId: number) => { return array as Array } -export const querySelectEntry = async (entryId: number) => { +export const getEntryById = async (entryId: number) => { return getBook_list_entry(entryId) } -export const queryInsertEntry = async (book_list_entry: Book_list_entry) => { +export const insertNewEntry = async (book_list_entry: Book_list_entry) => { mockBook_list_entryData.push({ id: idCounter++, list: book_list_entry.list, @@ -61,7 +61,7 @@ export const queryInsertEntry = async (book_list_entry: Book_list_entry) => { return true } -export const queryRemoveFromList = async (entryId: number) => { +export const removeEntryById = async (entryId: number) => { let deleted = false mockBook_list_entryData = mockBook_list_entryData.filter( (book_list_entry) => { diff --git a/src/queries/__mocks__/book_request.ts b/src/queries/__mocks__/book_request.ts new file mode 100644 index 0000000..2bebc70 --- /dev/null +++ b/src/queries/__mocks__/book_request.ts @@ -0,0 +1,83 @@ +import Book_request from '../../interfaces/book_request.interface' +import { Book_request_status } from '../../interfaces/book_request.interface' + +const mockBookRequests: Book_request[] = [ + { + id: 1, + userId: 1, + isbn: '978-1234567890', + title: 'Sample Book 1', + reason: 'I want to read this book.', + status: 0, + }, + { + id: 2, + userId: 2, + isbn: '978-9876543210', + title: 'Another Book', + reason: 'I need this for my research.', + status: 0, + }, + { + id: 3, + userId: 3, + isbn: '978-5678901234', + title: 'The Third Book', + reason: 'This book is a must-read!', + status: 2, + }, + { + id: 4, + userId: 1, + isbn: '978-3456789012', + title: 'Request Book 4', + reason: 'I heard its a great book.', + status: 0, + }, + { + id: 5, + userId: 2, + isbn: '978-2109876543', + title: 'Book Five', + reason: 'Please approve my request.', + status: 1, + }, +] + +export const getAllRequests = async (): Promise => { + return mockBookRequests +} + +export const insertRequest = async ( + userId: number, + isbn: string, + title: string, + reason: string +): Promise => { + let lengthBefore = mockBookRequests.length + mockBookRequests.push({ + id: mockBookRequests.length + 1, + userId: userId, + isbn: isbn, + title: title, + reason: reason, + status: 0, + }) + if (mockBookRequests.length > lengthBefore) { + return true + } + return false +} + +export const updateRequestStatus = async ( + id: number, + status: Book_request_status +): Promise => { + let index = mockBookRequests.findIndex((element) => element.id === id) + if (index < 0 && index > mockBookRequests.length) { + return false + } else { + mockBookRequests[index].status = status + return true + } +} diff --git a/src/queries/__mocks__/book_reservation.ts b/src/queries/__mocks__/book_reservation.ts new file mode 100644 index 0000000..d110612 --- /dev/null +++ b/src/queries/__mocks__/book_reservation.ts @@ -0,0 +1,281 @@ +import Book_reservation from '../../interfaces/book_reservation.interface' +import ExtendedReservation from '../../interfaces/extendedReservation.interface' + +import { mockUserData } from './user' +import { mockBookData } from './book' +import { mockBorrowData, isBookAvailable } from './borrow' +import { RESERVATION_DAYS, MS_IN_DAY } from '../../constants' + +// note: this mock is very basic and its purpose is for jest to run through routes + +let date = new Date() + +let reservation1: Book_reservation = { + id: 1, + bookId: 1, + userId: 1, + borrowId: 1, + reservationDatetime: date, + loaned: false, + canceled: false, +} + +let reservation2: Book_reservation = { + id: 2, + bookId: 2, + userId: 2, + borrowId: 2, + reservationDatetime: date, + loaned: true, + canceled: false, +} + +let reservation3: Book_reservation = { + id: 3, + bookId: 3, + userId: 3, + borrowId: 3, + reservationDatetime: date, + loaned: false, + canceled: false, +} + +let reservation4: Book_reservation = { + id: 4, + bookId: 1, + userId: 2, + borrowId: 4, + reservationDatetime: date, + loaned: false, + canceled: false, +} + +let reservation5: Book_reservation = { + id: 5, + bookId: 2, + userId: 3, + borrowId: 5, + reservationDatetime: date, + loaned: false, + canceled: false, +} + +let mockReservationsData = [ + reservation1, + reservation2, + reservation3, + reservation4, + reservation5, +] +let idCount = 5 + +const getUsername = async (userId: number): Promise => { + for (let index = 0; index < mockUserData.length; index++) { + if (mockUserData[index].id === userId) { + return mockUserData[index].username + } + } + return null +} + +const getBookTitle = async (bookId: number): Promise => { + for (let index = 0; index < mockBookData.length; index++) { + if (mockBookData[index].id === bookId) { + return mockBookData[index].title + } + } + return null +} + +const getReturnDate = async (borrowId: number): Promise => { + for (let index = 0; index < mockBorrowData.length; index++) { + if (mockBorrowData[index].id === borrowId) { + return mockBorrowData[index].returnDate + } + } + return null +} + +const filterValidReservations = (reservations: any) => { + return JSON.parse(JSON.stringify(reservations)).filter( + (reservation: ExtendedReservation) => + reservation.returnDate === null || + new Date( + new Date(reservation.returnDate).getTime() + + RESERVATION_DAYS * MS_IN_DAY + ).getTime() > new Date().getTime() + ) +} + +///bookreservation/all (GET) +export const getAllReservations = async (): Promise => { + let array: Array = [] + mockReservationsData.forEach((element) => { + array.push(element) + }) + return array +} + +///bookreservation/all/current +export const getCurrentReservations = async (): Promise< + ExtendedReservation[] +> => { + const array: Array = [] + for (let index = 0; index < mockReservationsData.length; index++) { + if ( + mockReservationsData[index].canceled !== true && + mockReservationsData[index].loaned !== true + ) { + let username = await getUsername(mockReservationsData[index].userId) + let title = await getBookTitle(mockReservationsData[index].bookId) + let returnDate = await getReturnDate(mockReservationsData[index].borrowId) + if (username !== null && title !== null) { + array.push({ + id: mockReservationsData[index].id, + username: username, + userId: mockReservationsData[index].userId, + title: title, + bookId: mockReservationsData[index].bookId, + reservationDatetime: mockReservationsData[index].reservationDatetime, + loaned: mockReservationsData[index].loaned, + canceled: mockReservationsData[index].canceled, + returnDate: returnDate, + }) + } + } + } + return filterValidReservations(array) as ExtendedReservation[] +} + +///bookreservation/all/extended +export const getAllExtendedReservations = async (): Promise< + ExtendedReservation[] +> => { + const array: Array = [] + for (let index = 0; index < mockReservationsData.length; index++) { + let username = await getUsername(mockReservationsData[index].userId) + let title = await getBookTitle(mockReservationsData[index].bookId) + let returnDate = await getReturnDate(mockReservationsData[index].borrowId) + if (username !== null && title !== null) { + array.push({ + id: mockReservationsData[index].id, + username: username, + userId: mockReservationsData[index].userId, + title: title, + bookId: mockReservationsData[index].bookId, + reservationDatetime: mockReservationsData[index].reservationDatetime, + loaned: mockReservationsData[index].loaned, + canceled: mockReservationsData[index].canceled, + returnDate: returnDate, + }) + } + } + return filterValidReservations(array) as ExtendedReservation[] +} + +///bookreservation/book (GET) +export const getCurrentReservationForBook = async (bookId: number) => { + const array: Array = [] + for (let index = 0; index < mockReservationsData.length; index++) { + if (mockReservationsData[index].bookId === bookId) { + let username = await getUsername(mockReservationsData[index].userId) + let title = await getBookTitle(mockReservationsData[index].bookId) + let returnDate = await getReturnDate(mockReservationsData[index].borrowId) + if (username !== null && title !== null) { + array.push({ + id: mockReservationsData[index].id, + username: username, + userId: mockReservationsData[index].userId, + title: title, + bookId: mockReservationsData[index].bookId, + reservationDatetime: mockReservationsData[index].reservationDatetime, + loaned: mockReservationsData[index].loaned, + canceled: mockReservationsData[index].canceled, + returnDate: returnDate, + }) + } + } + } + const validReservations = filterValidReservations(array) + return validReservations.length > 0 + ? (validReservations[0] as ExtendedReservation) + : null +} + +export const getUserCurrentExtendedReservations = async ( + userId: number +): Promise => { + const array: Array = [] + for (let index = 0; index < mockReservationsData.length; index++) { + if ( + mockReservationsData[index].bookId === userId && + mockReservationsData[index].loaned === false && + mockReservationsData[index].canceled === false + ) { + let username = await getUsername(mockReservationsData[index].userId) + let title = await getBookTitle(mockReservationsData[index].bookId) + let returnDate = await getReturnDate(mockReservationsData[index].borrowId) + if (username !== null && title !== null) { + array.push({ + id: mockReservationsData[index].id, + username: username, + userId: mockReservationsData[index].userId, + title: title, + bookId: mockReservationsData[index].bookId, + reservationDatetime: mockReservationsData[index].reservationDatetime, + loaned: mockReservationsData[index].loaned, + canceled: mockReservationsData[index].canceled, + returnDate: returnDate, + }) + } + } + } + const validReservations = filterValidReservations(array) + return validReservations.length > 0 + ? (validReservations as ExtendedReservation[]) + : null +} + +//Insert reservation +export const insertReservation = async ( + userId: number, + bookId: number +): Promise => { + const length = mockReservationsData.length + if (await getCurrentReservationForBook(bookId)) { + return false + } + const borrow = await isBookAvailable(bookId) + if (borrow === true) { + mockReservationsData.push({ + id: idCount + 1, + bookId: bookId, + userId: userId, + borrowId: mockBorrowData.length + 1, + reservationDatetime: date, + loaned: false, + canceled: false, + }) + } + return true +} + +//Cancel reservation +export const cancelReservation = async (bookId: number): Promise => { + mockReservationsData.forEach((element) => { + if (element.bookId === bookId) { + element.canceled = !element.canceled + } + }) + return true +} + +// /bookreservation/loan +export const loanReservation = async (bookId: number): Promise => { + mockReservationsData.forEach((element) => { + if (element.bookId === bookId) { + element.loaned = !element.loaned + } + }) + return true +} diff --git a/src/queries/__mocks__/book_review.ts b/src/queries/__mocks__/book_review.ts new file mode 100644 index 0000000..b152f4e --- /dev/null +++ b/src/queries/__mocks__/book_review.ts @@ -0,0 +1,134 @@ +import Book_review from '../../interfaces/book_review.interface' + +const date = new Date() + +const mockBookReviews: Book_review[] = [ + { + id: 1, + userId: 1, + bookId: 1, + comment: 'A great book!', + rating: 5, + reviewDate: date, + }, + { + id: 2, + userId: 2, + bookId: 2, + comment: 'Enjoyable read', + rating: 4, + reviewDate: date, + }, + { + id: 3, + userId: 3, + bookId: 1, + comment: 'Not my favorite', + rating: 3, + reviewDate: date, + }, + { + id: 4, + userId: 1, + bookId: 3, + comment: 'Loved it!', + rating: 5, + reviewDate: date, + }, + { + id: 5, + userId: 2, + bookId: 3, + comment: 'Highly recommended', + rating: 5, + reviewDate: date, + }, +] + +export const getAllReviews = async (): Promise => { + return mockBookReviews as Book_review[] +} + +export const getReviewByBookId = async ( + bookId: number +): Promise => { + let array: Array = [] + for (let index = 0; index < mockBookReviews.length; index++) { + if (mockBookReviews[index].bookId === bookId) { + array.push(mockBookReviews[index]) + } + } + return array.length > 0 ? (array as Book_review[]) : null +} + +export const deleteReview = async (id: number): Promise => { + let array: Array = [] + for (let index = 0; index < mockBookReviews.length; index++) { + if (mockBookReviews[index].id !== id) { + array.push(mockBookReviews[index]) + } + } + if (mockBookReviews.length > array.length) { + return true + } + return false +} + +export const updateReview = async ( + reviewId: number, + comment: string, + rating: number +): Promise => { + mockBookReviews.forEach((element) => { + if (element.id === reviewId) { + element.comment = comment + element.rating = rating + } + }) + return true +} + +export const insertReview = async ( + userId: number, + bookId: number, + comment: string, + rating: number +): Promise => { + mockBookReviews.push({ + id: mockBookReviews.length + 1, + userId: userId, + bookId: bookId, + comment: comment, + rating: rating, + reviewDate: new Date(), + }) + return true +} + +export const getAverageRatingForBook = async ( + bookId: number +): Promise => { + let totalRating: number = 0 + let numberOfReviews: number = 0 + let averageRating: number = 0 + mockBookReviews.forEach((element) => { + if (element.bookId === bookId) { + totalRating = totalRating + element.rating + numberOfReviews++ + } + }) + averageRating = totalRating / numberOfReviews + return averageRating +} + +export const getReviewById = async ( + reviewId: number +): Promise => { + let array: Array = [] + for (let index = 0; index < mockBookReviews.length; index++) { + if (mockBookReviews[index].id === reviewId) { + array.push(mockBookReviews[index]) + } + } + return array.length > 0 ? (array[0] as Book_review) : null +} diff --git a/src/queries/__mocks__/borrow.ts b/src/queries/__mocks__/borrow.ts index ffeaf20..58c0b90 100644 --- a/src/queries/__mocks__/borrow.ts +++ b/src/queries/__mocks__/borrow.ts @@ -24,7 +24,7 @@ const borrow2: Borrow = { borrowDate: new Date(), dueDate: new Date(tenDaysAhead), returnDate: null, - returned: false, + returned: true, } const borrow3: Borrow = { @@ -56,7 +56,7 @@ const detailedExpiredBorrow1: DetailedExpiredBorrow = { userId: borrow4.library_user, } -let mockBorrowData = [borrow1, borrow2, borrow3, borrow4] +export let mockBorrowData = [borrow1, borrow2, borrow3, borrow4] let mockDetailedExpiredBorrowData = [detailedExpiredBorrow1] let idCounter = 4 @@ -69,11 +69,11 @@ const getBorrow = (id: number) => { return null } -export const querySelectAllBorrows = async (): Promise => { +export const getAllBorrows = async (): Promise => { return mockBorrowData as Array } -export const querySelectAllCurrentBorrows = async (): Promise => { +export const getAllCurrentBorrows = async (): Promise => { let array: Array = [] mockBorrowData.forEach((element) => { if (!element.returned) { @@ -83,7 +83,7 @@ export const querySelectAllCurrentBorrows = async (): Promise => { return array as Array } -export const querySelectAllCurrentBorrows2 = async () => { +export const getAllCurrentDetailedBorrows = async () => { let array: { username: string title: string @@ -105,15 +105,13 @@ export const querySelectAllCurrentBorrows2 = async () => { return array } -export const querySelectBorrow = async ( +export const getBorrowById = async ( borrowingId: number ): Promise => { return getBorrow(borrowingId) } -export const queryDeleteBorrow = async ( - borrowingId: number -): Promise => { +export const deleteBorrow = async (borrowingId: number): Promise => { let deleted = false mockBorrowData = mockBorrowData.filter((borrow) => { if (borrow.id == borrowingId) deleted = true @@ -122,7 +120,7 @@ export const queryDeleteBorrow = async ( return deleted } -export const queryInsertBorrow = async ( +export const insertBorrow = async ( userId: number, bookId: number, dueDate: Date, @@ -140,7 +138,7 @@ export const queryInsertBorrow = async ( return true } -export const queryUpdateBorrow = async (borrow: Borrow): Promise => { +export const updateBorrow = async (borrow: Borrow): Promise => { const editedBorrow = getBorrow(borrow.id) if (editedBorrow) { editedBorrow.book = borrow.book @@ -152,9 +150,7 @@ export const queryUpdateBorrow = async (borrow: Borrow): Promise => { return false } -export const queryBookIsAvailable = async ( - bookId: number -): Promise => { +export const isBookAvailable = async (bookId: number): Promise => { const borrow = getBorrow(bookId) let availCheck = 0 if (borrow) { @@ -165,9 +161,7 @@ export const queryBookIsAvailable = async ( return availCheck == 0 } -export const queryBorrowsByUserId = async ( - userId: number -): Promise => { +export const getBorrowsByUserId = async (userId: number): Promise => { let array: Array = [] const borrow = getBorrow(userId) if (borrow) { @@ -178,7 +172,7 @@ export const queryBorrowsByUserId = async ( return array as Array } -export const queryExpiredBorrows = async (): Promise => { +export const getExpiredBorrows = async (): Promise => { let array: Array = [] mockBorrowData.forEach((element) => { if (!element.returned && new Date() > element.dueDate) { @@ -188,7 +182,7 @@ export const queryExpiredBorrows = async (): Promise => { return array as Array } -export const queryDetailedExpiredBorrows = async (): Promise< +export const getDetailedExpiredBorrows = async (): Promise< DetailedExpiredBorrow[] > => { return mockDetailedExpiredBorrowData as Array diff --git a/src/queries/__mocks__/office.ts b/src/queries/__mocks__/office.ts new file mode 100644 index 0000000..a612a2c --- /dev/null +++ b/src/queries/__mocks__/office.ts @@ -0,0 +1,94 @@ +import { HomeOffice } from '../../interfaces/HomeOffice' + +let mockHomeOffices: HomeOffice[] = [ + { + id: 1, + name: 'Home Office 1', + countryCode: 'USA', + }, + { + id: 2, + name: 'Home Office 2', + countryCode: 'CAN', + }, + { + id: 3, + name: 'Home Office 3', + countryCode: 'GBR', + }, + { + id: 4, + name: 'Home Office 4', + countryCode: 'FRA', + }, + { + id: 5, + name: 'Home Office 5', + countryCode: 'DEU', + }, +] + +let idCount: number = 5 + +export async function getHomeOfficeById( + homeOfficeId: number +): Promise { + let array: Array = [] + mockHomeOffices.forEach((element) => { + if (element.id === homeOfficeId) { + array.push(element) + } + }) + return array.length ? array[0] : null +} + +export async function getAllHomeOffices(): Promise { + return !mockHomeOffices.length ? [] : mockHomeOffices.map((value) => value) +} + +export async function deleteHomeOffice(homeOfficeId: number): Promise { + mockHomeOffices = mockHomeOffices.filter( + (element) => element.id !== homeOfficeId + ) + let index = mockHomeOffices.findIndex( + (element) => element.id === homeOfficeId + ) + for (let i = 0; i < mockHomeOffices.length; i++) { + if (mockHomeOffices[i].id === index) { + return false + } + } + return true +} + +export async function updateHomeOffice( + homeOffice: HomeOffice +): Promise { + for (let index = 0; index < mockHomeOffices.length; index++) { + if (mockHomeOffices[index].id === homeOffice.id) { + mockHomeOffices[index] = { + id: mockHomeOffices[index].id, + name: homeOffice.name, + countryCode: homeOffice.countryCode, + } + return true + } + } + return false +} + +export async function insertHomeOffice( + name: string, + countryCode: string +): Promise { + let length = mockHomeOffices.length + mockHomeOffices.push({ + id: idCount + 1, + name: name, + countryCode: countryCode, + }) + if (mockHomeOffices.length > length) { + return true + } + return false +} diff --git a/src/queries/__mocks__/session.ts b/src/queries/__mocks__/session.ts index 138b3b6..f84e65e 100644 --- a/src/queries/__mocks__/session.ts +++ b/src/queries/__mocks__/session.ts @@ -1,17 +1,15 @@ import Session from '../../interfaces/session.interface' -async function querySelectSessionBySecret( - secret: string -): Promise { +async function getSessionBySecret(secret: string): Promise { const currentTime = new Date().getTime() / 1000 const session: Session = { id: 1, userId: 1, secret: '123', - expires: currentTime + 1234, + expires: currentTime + 123467, invalidated: false, } return session.secret === secret ? session : null } -export { querySelectSessionBySecret } +export { getSessionBySecret } diff --git a/src/queries/__mocks__/user.ts b/src/queries/__mocks__/user.ts index dc7521b..bcd5ef5 100644 --- a/src/queries/__mocks__/user.ts +++ b/src/queries/__mocks__/user.ts @@ -7,7 +7,7 @@ const user1: User = { username: 't1', email: 't1@t.est', passw: 'p1', - administrator: false, + administrator: true, deleted: false, } const user2: User = { @@ -15,7 +15,7 @@ const user2: User = { username: 't2', email: 't2@t.est', passw: 'p2', - administrator: true, + administrator: false, deleted: false, } const user3: User = { @@ -26,9 +26,9 @@ const user3: User = { administrator: false, deleted: true, } -const mockUserData = [user1, user2, user3] +export const mockUserData = [user1, user2, user3] -export const querySelectAllExistingUsers = async (userId: string) => { +export const getAllActiveUsers = async () => { let array: Array = [] mockUserData.forEach((element) => { if (!element.deleted) { @@ -38,11 +38,11 @@ export const querySelectAllExistingUsers = async (userId: string) => { return array as Array } -export const querySelectAllUsers = async () => { +export const getAllUsers = async () => { return mockUserData as Array } -export const querySelectUser = async (userId: number) => { +export const getUserById = async (userId: number) => { for (let index = 0; index < mockUserData.length; index++) { if (mockUserData[index].id === userId) { return mockUserData[index] @@ -51,11 +51,11 @@ export const querySelectUser = async (userId: number) => { return null } -export const querySelectUserBySessionId = async (sessionId: number) => { - return mockUserData[1] +export const getUserBySessionId = async (sessionId: number) => { + return mockUserData[0] } -export const querySelectUserByUsername = async (username: string) => { +export const getUserByUsername = async (username: string) => { for (let index = 0; index < mockUserData.length; index++) { if (mockUserData[index].username === username) { return mockUserData[index] @@ -64,15 +64,15 @@ export const querySelectUserByUsername = async (username: string) => { return null } -export const queryHardDeleteUser = async (userId: string) => { +export const deleteUserHard = async (userId: string) => { return true } -export const querySoftDeleteUser = async (userId: string) => { +export const deleteUserSoft = async (userId: string) => { return true } -export const queryInsertUser = async ( +export const insertUser = async ( username: string, email: string, password: string, @@ -89,6 +89,6 @@ export const queryInsertUser = async ( : null } -export const queryUpdateUser = async (user: User) => { +export const updateUser = async (user: User) => { return user.username === 'testy' } diff --git a/src/queries/book.ts b/src/queries/book.ts index 4d44ae5..524a505 100644 --- a/src/queries/book.ts +++ b/src/queries/book.ts @@ -5,7 +5,7 @@ import Book from '../interfaces/book.interface' export const getBookById = async (bookId: number): Promise => { const promisePool = pool.promise() const [rows] = await promisePool.query( - 'SELECT book.*, ho.home_office_id AS homeOfficeId, ho.name AS homeOfficeName, ho.country_code AS homeOfficeCountry FROM book JOIN home_office ho USING (home_office_id) WHERE book.id = ?', + 'SELECT book.*, ho.home_office_id AS homeOfficeId, ho.name AS homeOfficeName, ho.country_code AS homeOfficeCountry FROM book LEFT JOIN home_office ho USING (home_office_id) WHERE book.id = ?', [bookId] ) return rows.length > 0 ? (rows[0] as Book) : null @@ -14,7 +14,7 @@ export const getBookById = async (bookId: number): Promise => { export const getAllExistingBooks = async (): Promise => { const promisePool = pool.promise() const [rows] = await promisePool.query( - 'SELECT book.*, ho.home_office_id AS homeOfficeId, ho.name AS homeOfficeName, ho.country_code AS homeOfficeCountry FROM book JOIN home_office ho USING (home_office_id) WHERE deleted != 1;' + 'SELECT book.*, ho.home_office_id AS homeOfficeId, ho.name AS homeOfficeName, ho.country_code AS homeOfficeCountry FROM book LEFT JOIN home_office ho USING (home_office_id) WHERE deleted != 1;' ) return rows as Book[] } @@ -27,7 +27,7 @@ export const getAllBooksPaged = async ( let start = (page - 1) * size const promisePool = pool.promise() const [rows] = await promisePool.query( - 'SELECT book.*, ho.home_office_id AS homeOfficeId, ho.name AS homeOfficeName, ho.country_code AS homeOfficeCountry FROM book JOIN home_office ho USING (home_office_id) WHERE deleted != 1 limit ? offset ?', + 'SELECT book.*, ho.home_office_id AS homeOfficeId, ho.name AS homeOfficeName, ho.country_code AS homeOfficeCountry FROM book LEFT JOIN home_office ho USING (home_office_id) WHERE deleted != 1 limit ? offset ?', [size, start] ) return rows as Book[] @@ -125,3 +125,15 @@ export const getAllReservedBooks = async (): Promise => { ) return rows as Book[] } + +export const updateBooksOffice = async ( + newOfficeId: number, + oldOfficeId: number +): Promise => { + const query = 'UPDATE book SET home_office_id = ? WHERE home_office_id = ?' + const values = [newOfficeId, oldOfficeId] + + const promisePool = pool.promise() + const [rows] = await promisePool.query(query, values) + return rows.affectedRows !== 0 +} diff --git a/src/routes/book.ts b/src/routes/book.ts index 03d779a..4d08352 100644 --- a/src/routes/book.ts +++ b/src/routes/book.ts @@ -8,6 +8,7 @@ import { getAllReservedBooks, getAllBooksPaged, getCountOfAllBooks, + updateBooksOffice, } from '../queries/book' import Book from '../interfaces/book.interface' @@ -128,4 +129,19 @@ router.get( } ) +router.put('/', async (req: Request, res: Response, next: NextFunction) => { + try { + const { newOfficeId, oldOfficeId } = req.body + + if (req.sessionUser.administrator) { + const ok = await updateBooksOffice(newOfficeId, oldOfficeId) + res.json({ ok }) + } else { + res.status(403).json({ ok: false }) + } + } catch (err) { + next(err) + } +}) + export default router diff --git a/src/routes/book_reservation.ts b/src/routes/book_reservation.ts index 5525370..08624ab 100644 --- a/src/routes/book_reservation.ts +++ b/src/routes/book_reservation.ts @@ -70,7 +70,7 @@ router.post( async (req: Request, res: Response, next: NextFunction) => { try { res.json({ - ok: await cancelReservation(req.body.bookId), + ok: await cancelReservation(req.body.reservationId), }) } catch (err) { next(err) @@ -83,7 +83,7 @@ router.post( async (req: Request, res: Response, next: NextFunction) => { try { res.json({ - ok: await loanReservation(req.body.bookId), + ok: await loanReservation(req.body.reservationId), }) } catch (err) { next(err) diff --git a/src/routes/borrow.ts b/src/routes/borrow.ts index f375b1e..1661923 100644 --- a/src/routes/borrow.ts +++ b/src/routes/borrow.ts @@ -13,6 +13,7 @@ import { getDetailedExpiredBorrows, } from '../queries/borrow' import Borrow from '../interfaces/borrow.interface' +import { getCurrentReservationForBook } from '../queries/book_reservation' const BORROW_LENGTH = 10 @@ -52,7 +53,10 @@ router.delete('/', async (req: Request, res: Response, next: NextFunction) => { router.post('/', async (req: Request, res: Response, next: NextFunction) => { try { let bookAvailable = await isBookAvailable(req.body.bookId) - if (bookAvailable) { + let bookReservationStatus = await getCurrentReservationForBook( + req.body.bookId + ) + if (bookAvailable && bookReservationStatus == null) { let dueDate = new Date() dueDate.setDate(dueDate.getDate() + BORROW_LENGTH) res.json({ diff --git a/test/routes/book.test.ts b/test/routes/book.test.ts index ba48252..e513d13 100644 --- a/test/routes/book.test.ts +++ b/test/routes/book.test.ts @@ -4,12 +4,13 @@ import { app, pool } from '../../src' jest.mock('../../src/queries/session') jest.mock('../../src/queries/book') +jest.mock('../../src/queries/user') describe('basic endpoint testing for /book', () => { test('get /book/all', async () => { return request(app) .get('/book/all') - .set('Authorization', `Bearer 123`) + .set('Authorization', 'Bearer 123') .expect(200) .expect('Content-Type', /json/) }) @@ -23,20 +24,28 @@ describe('basic endpoint testing for /book', () => { }) test('delete /book', async () => { - return ( - request(app) - .delete('/book?id=1') - .set('Authorization', `Bearer 123`) - //.expect(200) - //.expect("Content-Type", /json/); - .then(() => { + return request(app) + .delete('/book?id=1') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + /** + * .then(() => { expect(200) console.log('book delete test sucessful') }) .catch((error) => { console.error('failed: ', error) }) - ) + */ + + test('get /book/count', async () => { + return request(app) + .get('/book/count') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) }) test('post /book', async () => { @@ -53,7 +62,7 @@ describe('basic endpoint testing for /book', () => { }) .set('Authorization', `Bearer 123`) .expect(200) - .expect({ ok: true }) + .expect('Content-Type', /json/) }) test('put /book', async () => { diff --git a/test/routes/book_favorite.test.ts b/test/routes/book_favorite.test.ts new file mode 100644 index 0000000..34be79c --- /dev/null +++ b/test/routes/book_favorite.test.ts @@ -0,0 +1,50 @@ +import { test, describe, jest, afterAll } from '@jest/globals' +import request from 'supertest' +import { app, pool } from '../../src' + +jest.mock('../../src/queries/session') +jest.mock('../../src/queries/book_favorite') +jest.mock('../../src/queries/user') + +describe('basic endpoint testing for /favorite', () => { + test('get /favorite/check', async () => { + return request(app) + .get('/favorite/check') + .query({ bookId: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ isFavorited: true }) + }) + + test('get /favorite/count', async () => { + return request(app) + .get('/favorite/count') + .query({ bookId: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ count: 2 }) + }) + + test('add favorite book to user /favorite', async () => { + return request(app) + .post('/favorite/') + .send({ bookId: 3 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + + test('delete favorite book from user /favorite', async () => { + return request(app) + .delete('/favorite/') + .send({ bookId: 2 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) +}) + +afterAll((done) => { + pool.end() + done() +}) diff --git a/test/routes/book_list.test.ts b/test/routes/book_list.test.ts index 767c777..82ae32c 100644 --- a/test/routes/book_list.test.ts +++ b/test/routes/book_list.test.ts @@ -4,6 +4,7 @@ import { app, pool } from '../../src' jest.mock('../../src/queries/session') jest.mock('../../src/queries/book_list') +jest.mock('../../src/queries/user') describe('basic endpoint testing for /booklist', () => { test('get /booklist/all', async () => { diff --git a/test/routes/book_list_entry.test.ts b/test/routes/book_list_entry.test.ts index ab9ca11..88f888f 100644 --- a/test/routes/book_list_entry.test.ts +++ b/test/routes/book_list_entry.test.ts @@ -4,6 +4,7 @@ import { app, pool } from '../../src' jest.mock('../../src/queries/session') jest.mock('../../src/queries/book_list_entry') +jest.mock('../../src/queries/user') describe('basic endpoint testing for /booklistentry', () => { test('get /booklistentry/all', async () => { diff --git a/test/routes/book_request.test.ts b/test/routes/book_request.test.ts new file mode 100644 index 0000000..8f6f67c --- /dev/null +++ b/test/routes/book_request.test.ts @@ -0,0 +1,44 @@ +import { test, describe, jest, afterAll } from '@jest/globals' +import request from 'supertest' +import { app, pool } from '../../src' + +jest.mock('../../src/queries/session') +jest.mock('../../src/queries/book_request') +jest.mock('../../src/queries/user') + +describe('basic endpoint testing for /bookrequest', () => { + test('get all requests /bookrequest/all', async () => { + return request(app) + .get('/bookrequest/all') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('add new request for book /bookrequest', async () => { + return request(app) + .post('/bookrequest/') + .send({ + isbn: '9789206334843', + title: 'Cars', + reason: 'It helps me focus', + }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + + test('update request status /bookrequest/updatestatus', async () => { + return request(app) + .put('/bookrequest/updatestatus') + .send({ id: 2, status: 2 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) +}) + +afterAll((done) => { + pool.end() + done() +}) diff --git a/test/routes/book_reservation.test.ts b/test/routes/book_reservation.test.ts new file mode 100644 index 0000000..7f3740f --- /dev/null +++ b/test/routes/book_reservation.test.ts @@ -0,0 +1,83 @@ +import { test, describe, jest, afterAll } from '@jest/globals' +import request from 'supertest' +import { app, pool } from '../../src' + +jest.mock('../../src/queries/session') +jest.mock('../../src/queries/book_reservation') +jest.mock('../../src/queries/user') + +describe('basic endpoint testing for /bookreservation', () => { + test('get /bookreservation/all', async () => { + return request(app) + .get('/bookreservation/all') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('get /bookreservation/all/current', async () => { + return request(app) + .get('/bookreservation/all/current') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('get /bookreservation/all/extended', async () => { + return request(app) + .get('/bookreservation/all/extended') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('get /bookreservation/user/current', async () => { + return request(app) + .post('/bookreservation/user/current') + .send({ userId: 3 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('get /bookreservation/book', async () => { + return request(app) + .get('/bookreservation/book') + .send({ bookId: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('add new reservation /bookreservation/', async () => { + return request(app) + .post('/bookreservation/') + .send({ userId: 4, bookId: 5 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + + test('cancel reservation /bookreservation/cancel', async () => { + return request(app) + .post('/bookreservation/cancel') + .send({ reservationId: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + + test('loan reservation /bookreservation/loan', async () => { + return request(app) + .post('/bookreservation/loan') + .send({ reservationId: 3 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) +}) + +afterAll((done) => { + pool.end() + done() +}) diff --git a/test/routes/book_review.test.ts b/test/routes/book_review.test.ts new file mode 100644 index 0000000..3f987e8 --- /dev/null +++ b/test/routes/book_review.test.ts @@ -0,0 +1,76 @@ +import { test, describe, jest, afterAll } from '@jest/globals' +import request from 'supertest' +import { app, pool } from '../../src' + +jest.mock('../../src/queries/session') +jest.mock('../../src/queries/book_review') +jest.mock('../../src/queries/user') + +describe('basic endpoint testing for /review', () => { + test('get all bookreviews /review/all', async () => { + return request(app) + .get('/review/all') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('get review by bookId /review/book', async () => { + return request(app) + .get('/review/book') + .query({ bookId: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('get review average for book /review/average', async () => { + return request(app) + .get('/review/average') + .query({ bookId: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ averageRating: 4 }) + }) + + test('delete review /review', async () => { + return request(app) + .delete('/review/') + .send({ reviewId: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + + test('delete non-existing review /review', async () => { + return request(app) + .delete('/review/') + .send({ reviewId: 16 }) + .set('Authorization', `Bearer 123`) + .expect(404) + .expect({ ok: false, error: 'Review not found.' }) + }) + + test('add review /review', async () => { + return request(app) + .post('/review/') + .send({ bookId: 1, comment: 'Nice book for testing', rating: 5 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + + test('update review /review', async () => { + return request(app) + .put('/review/') + .send({ rewiewId: 5, comment: 'Nevermind', rating: 1 }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) +}) + +afterAll((done) => { + pool.end() + done() +}) diff --git a/test/routes/borrow.test.ts b/test/routes/borrow.test.ts index fef2434..04bd54d 100644 --- a/test/routes/borrow.test.ts +++ b/test/routes/borrow.test.ts @@ -5,6 +5,7 @@ import { app, pool } from '../../src' jest.mock('../../src/queries/session') jest.mock('../../src/queries/borrow') jest.mock('../../src/queries/book') +jest.mock('../../src/queries/user') describe('basic endpoint testing for /borrow', () => { test('get /borrow/all', async () => { diff --git a/test/routes/office.test.ts b/test/routes/office.test.ts new file mode 100644 index 0000000..217495e --- /dev/null +++ b/test/routes/office.test.ts @@ -0,0 +1,56 @@ +import { test, describe, jest, afterAll } from '@jest/globals' +import request from 'supertest' +import { app, pool } from '../../src' + +jest.mock('../../src/queries/session') +jest.mock('../../src/queries/office') +jest.mock('../../src/queries/user') + +describe('basic endpoint testing for /office', () => { + test('get all offices /office/all', async () => { + return request(app) + .get('/office/all') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('Content-Type', /json/) + }) + + test('get office based on id /office/:homeOfficeId', async () => { + return request(app) + .get('/office/1') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ id: 1, name: 'Home Office 1', countryCode: 'USA' }) + }) + + test('delete office /office/:homeOfficeId', async () => { + return request(app) + .delete('/office/2') + .set('Authorization', `Bearer 123`) + .expect(200) + .expect('true') + }) + + test('update office /office/:homeOfficeId', async () => { + return request(app) + .put('/office/1') + .send({ name: 'Uzbekistan', countryCode: 'UZB' }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) + + test('insert office /office', async () => { + return request(app) + .post('/office/') + .send({ name: 'Tadzikistan', countryCode: 'TZK' }) + .set('Authorization', `Bearer 123`) + .expect(200) + .expect({ ok: true }) + }) +}) + +afterAll((done) => { + pool.end() + done() +}) diff --git a/test/routes/user.test.ts b/test/routes/user.test.ts index 8297a46..4168a13 100644 --- a/test/routes/user.test.ts +++ b/test/routes/user.test.ts @@ -30,13 +30,14 @@ describe('tests for route /user', () => { .expect('Content-Type', /json/) }) - test('delete /user', async () => { + /**test('delete /user', async () => { return request(app) .delete('/user?id=1') .set('Authorization', `Bearer 123`) .expect(200) .expect('Content-Type', /json/) }) + */ test('post /user //not admin', async () => { return (