diff --git a/server/jest.config.ts b/server/jest.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..53d87521050ddc677ddb885b6ed16334d50467ff --- /dev/null +++ b/server/jest.config.ts @@ -0,0 +1,197 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/en/configuration.html + */ + +export default { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "C:\\Users\\phucj\\AppData\\Local\\Temp\\jest", + + // Automatically clear mock calls and instances between every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + coverageDirectory: "coverage", + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // Indicates which provider should be used to instrument code for coverage + // coverageProvider: "babel", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "json", + // "jsx", + // "ts", + // "tsx", + // "node" + // ], + moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'json'], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state between every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state between every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "<rootDir>" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + testEnvironment: "node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jasmine2", + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + // testURL: "http://localhost", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + transform: { + '^.+\\.tsx?$': 'ts-jest' + }, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "\\\\node_modules\\\\", + // "\\.pnp\\.[^\\\\]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/server/package.json b/server/package.json index d3e68d2c3d4ec28b509b1549a945dcc45fbe6ab5..ddee43554ba1ad65283c4c99422e5e6a4cc92e65 100644 --- a/server/package.json +++ b/server/package.json @@ -5,19 +5,24 @@ "main": "src/index.ts", "scripts": { "start": "nodemon --watch 'src/**/*.ts' --exec \"ts-node\" src/index.ts", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest" }, "author": "", "license": "ISC", "dependencies": { "@types/cors": "^2.8.9", "@types/express": "^4.17.11", + "@types/jest": "^26.0.20", "@types/mysql": "^2.15.17", + "@types/supertest": "^2.0.10", "body-parser": "^1.19.0", "cors": "^2.8.5", "express": "^4.17.1", + "jest": "^26.6.3", "mysql": "^2.18.1", - "mysql2": "^2.2.5" + "mysql2": "^2.2.5", + "supertest": "^6.1.3", + "ts-jest": "^26.5.1" }, "devDependencies": { "nodemon": "^2.0.7", diff --git a/server/src/app.ts b/server/src/app.ts new file mode 100644 index 0000000000000000000000000000000000000000..366a65553d7703169d63f10d608cff484411bc2a --- /dev/null +++ b/server/src/app.ts @@ -0,0 +1,19 @@ +import express, { Application } from 'express'; +import routes from "./routes/routes"; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import mysql from 'mysql2'; +// Boot express +const app: Application = express(); + +app.use(cors()); +// Configuring body parser middleware +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); + +// Configuring json response +app.set("json spaces", 2); + +app.use("/api", routes); + +export default app; \ No newline at end of file diff --git a/server/src/controllers/categoryController/index.ts b/server/src/controllers/categoryController/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..6d9d91a1d812ac99c00af092a9ed2928a80d678f --- /dev/null +++ b/server/src/controllers/categoryController/index.ts @@ -0,0 +1,65 @@ +import { Response, Request } from "express"; +import query from '../../services/db_query'; +import express from 'express'; +import Category from '../../models/category'; + +const router = express.Router(); +const category = new Category(); +// routes Kategori +// - hente alle kategorier (get) +// SELECT * FROM category; +// - hente et bestemt kategori (get) +// SELECT * FROM category WHERE categoryid = #num; +// - remove specific cateogry (post) +// INSERT INTO `jonnynl_tdt4140`.`category` (`categoryid`, `navn`) VALUES ('4', 'ad'); +// - add category (post) +// DELETE FROM `jonnynl_tdt4140`.`category` WHERE (`categoryid` = '3'); + +/* ============================= CREATE ============================= */ +// Create category `/api/category/` +router.route('/').post(async (request: Request, response: Response) => { + const {category} = request.body; + try { + const input = (` INSERT INTO category(navn) VALUES (?);`) + return response.status(200).json( + await query(input,[category]) + ); + } catch (error) { + return response.status(400).send("Bad Request"); + } +}); + +/* ============================= READ ============================= */ +// Get category `/api/category/` +router.route('/').get(async (_: Request, response: Response) => { + try { + response.status(200).json(await query("SELECT * FROM category;","")); + } catch (error) { + response.status(400).send("Bad Request"); + } +}); + +// Get category from id `/api/category/:categoryid` +router.route('/:categoryid').get(async (request: Request, response: Response) => { + const categoryid = request.params.categoryid; + try { + response.status(200).json(await query("SELECT * FROM category WHERE categoryid = ?",[categoryid])); + } catch (error) { + response.status(400).send("Bad Request"); + } +}); + +/* ============================= UPDATE ============================= */ + +/* ============================= DELETE ============================= */ +// remove category with id `/api/category/#categoryid` +router.route('/').delete(async (request: Request, response: Response) => { + const categoryid = request.params.categoryid; + try { + response.status(200).json(await query("DELETE FROM category WHERE categoryid = ?",[categoryid])); + } catch (error) { + response.status(400).send("Bad Request"); + } +}); + +export default router; \ No newline at end of file diff --git a/server/src/controllers/categoryController/tests/categoryController.test.ts b/server/src/controllers/categoryController/tests/categoryController.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..a0283e72cfa8e64a2d2820393b0f34a249143e72 --- /dev/null +++ b/server/src/controllers/categoryController/tests/categoryController.test.ts @@ -0,0 +1,25 @@ +import request from 'supertest'; +import app from '../../../app'; + +describe('Test categoryController', () => { + beforeAll(async () => { + console.log("Test starting..."); + }); + + afterAll(async () => { + console.log("...Test ending"); + }); + + it('Request /api/category should return request of 200!', async () => { + const result = await request(app).get('/api/category').send(); + + expect(result.status).toBe(200); + }); + + it('Request /api/category/1 should return data with name "Antikviteter og Kunst"!', async () => { + const result = await request(app).get('/api/category/1').send(); + + expect(result.status).toBe(200); + expect(result.body.data[0]?.navn).toBe('Antikviteter og Kunst'); + }); +}); \ No newline at end of file diff --git a/server/src/controllers/postController.ts b/server/src/controllers/postcontroller/index.ts similarity index 69% rename from server/src/controllers/postController.ts rename to server/src/controllers/postcontroller/index.ts index 5651ca2fbda93670b3ed4de90206eb7a572e20f7..c437f2c608dd9d3087ecc1099a3544a4585f5a7b 100644 --- a/server/src/controllers/postController.ts +++ b/server/src/controllers/postcontroller/index.ts @@ -1,32 +1,28 @@ import { Response, Request } from "express"; -import query from '../services/db_query'; +import query from '../../services/db_query'; import express from 'express'; +import IPost from '../../models/post'; +import Category from '../../models/category'; + const router = express.Router(); +const category = new Category(); -interface IPost { - title: string; - description: string; - timestamp: number; - owner: string; - categoryid: number; - imageUrl: string; -} /* ============================= CREATE ============================= */ // Create posts `/api/post/` //'{"title":"test3","description":"test3","timestamp":123123,"owner":"test3","category":"test3","imageUrl":"test3"}' router.route('/').post(async (request: Request, response: Response) => { - const {title, description, timestamp, owner, categoryid, imageUrl} = request.body; + const {title, description, timestamp, owner, category, imageUrl} = request.body; try { const post: IPost = { "title": title, "description": description, "timestamp": timestamp, "owner": owner, - "categoryid": categoryid, + "category": category, "imageUrl": imageUrl }; if (Object.values(post).filter(p => p == undefined).length > 0) return response.status(500).send("Error"); - const input = (`INSERT INTO post(title, description, timestamp, owner, categoryid, imageUrl) VALUES (?,?,?,?,?,?)`) + const input = (`INSERT INTO post(title, description, timestamp, owner, category, imageUrl) VALUES (?,?,?,?,?,?)`) return response.status(200).json( await query(input,Object.values(post)) ); @@ -39,7 +35,11 @@ router.route('/').post(async (request: Request, response: Response) => { // Get all posts `/api/post/` router.route('/').get(async (_: Request, response: Response) => { try { - response.status(200).json(await query("SELECT * FROM post;","")); + //response.status(200).json(await query("SELECT * FROM post;","")); + const input = `SELECT p.id, p.title, p.description, p.timestamp, p.owner, category.navn, p.imageUrl + FROM post as p + INNER JOIN category ON category.categoryid = p.categoryid;` + response.status(200).json(await query(input,"")); } catch (error) { response.status(400).send("Bad Request"); } @@ -49,7 +49,11 @@ router.route('/').get(async (_: Request, response: Response) => { router.route('/:id').get(async (request: Request, response: Response) => { const postId = request.params.id; try { - response.status(200).json(await query("SELECT * FROM post WHERE id=?;",[postId])); + //response.status(200).json(await query("SELECT * FROM post WHERE id=?;",[postId])); + const input = `SELECT p.id, p.title, p.description, p.timestamp, p.owner, category.navn, p.imageUrl + FROM post as p + INNER JOIN category ON category.categoryid = p.categoryid WHERE p.id=?;` + response.status(200).json(await query(input,[postId])); } catch (error) { response.status(400).send("Bad Request"); } diff --git a/server/src/index.ts b/server/src/index.ts index 8d875a0e68cc187dc3a09d3cdc5de1ad52772847..d4b49774f337ac7b92acab230b4e8fe2970b442e 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,23 +1,8 @@ -import bodyParser from 'body-parser'; -import cors from 'cors'; -import express from 'express'; -import routes from './routes/routes'; -import mysql from 'mysql2'; +import app from './app'; // REST API config -const app = express(); const port = 3000; -app.use(cors()); -// Configuring body parser middleware -app.use(bodyParser.urlencoded({ extended: false })); -app.use(bodyParser.json()); - -// Configuring json response -app.set("json spaces", 2); - -app.use("/api", routes); - app.listen(port, () => { console.log(`Listening on port ${port}!`) -}); +}); \ No newline at end of file diff --git a/server/src/models/category.ts b/server/src/models/category.ts new file mode 100644 index 0000000000000000000000000000000000000000..be930c47a7b34d318dc14240ab7ffcb3534039ea --- /dev/null +++ b/server/src/models/category.ts @@ -0,0 +1,26 @@ +class Category { + private list: string[] = [ + + ] + + // add a category to the category list + addToList(category: string) { + this.list.push(category); + } + + // remove a category from the category list + removeFromList(category: string) { + try { + delete this.list[this.list.indexOf(category)]; + } catch(e) { + throw new Error("Couldnt delete category"); + } + } + + // return the list of categories. + getList() { + return this.list; + } +} + +export default Category; \ No newline at end of file diff --git a/server/src/models/post.ts b/server/src/models/post.ts new file mode 100644 index 0000000000000000000000000000000000000000..146bf71067332d191d975514b24837200d3d3706 --- /dev/null +++ b/server/src/models/post.ts @@ -0,0 +1,12 @@ +// Post modell, med typene: +interface IPost { + title: string; + description: string; + timestamp: number; + owner: string; + category: string; + imageUrl: string; +} + +// Eksporterer IPost til bruk i andre filer. +export default IPost; \ No newline at end of file diff --git a/server/src/routes/routes.ts b/server/src/routes/routes.ts index 49efb2c37e47305c88c0a75208a7f526e2746528..aec88f548f1ea10d2c42f362a6d057ba586f0fdd 100644 --- a/server/src/routes/routes.ts +++ b/server/src/routes/routes.ts @@ -1,11 +1,12 @@ -//import { example1, example2 } from '../controllers/example'; -import postController from '../controllers/postController' +import postController from '../controllers/postcontroller'; +import categoryController from '../controllers/categoryController'; import express from 'express'; const router = express.Router(); // Endpoints -router.use("/post", postController); +router.use("/post", postController); +router.use("/category", categoryController); export default router;