--- id: 587d8249367417b2b2512c42 title: Issue Tracker challengeType: 4 forumTopicId: 301569 --- ## Description
Build a full stack JavaScript app that is functionally similar to this: https://issue-tracker.freecodecamp.rocks/. Working on this project will involve you writing your code using one of the following methods: - Clone this GitHub repo and complete your project locally. - Use this repl.it starter project to complete your project. - Use a site builder of your choice to complete the project. Be sure to incorporate all the files from our GitHub repo. When you are done, make sure a working demo of your project is hosted somewhere public. Then submit the URL to it in the `Solution Link` field. Optionally, also submit a link to your project's source code in the `GitHub Link` field.
## Instructions
- Complete the neccessary routes in `/routes/api.js` - Create all of the functional tests in `tests/2_functional-tests.js` - Copy the `sample.env` file to `.env` and set the variables appropriately - To run the tests uncomment `NODE_ENV=test` in your `.env` file - To run the tests in the console, use the command `npm run test`. To open the Repl.it console, press Ctrl+Shift+P (Cmd if on a Mac) and type "open shell"
## Tests
```yml tests: - text: You can provide your own project, not the example URL. testString: " getUserInput => { assert(!/.*\\/issue-tracker\\.freecodecamp\\.rocks/.test(getUserInput('url'))); }" - text: You can send a `POST` request to `/api/issues/{projectname}` with form data containing the required fields `issue_title`, `issue_text`, `created_by`, and optionally `assigned_to` and `status_text`. testString: 'async getUserInput => { try { let test_data = { issue_title: "Faux Issue Title", issue_text: "Functional Test - Required Fields Only", created_by: "fCC", }; const data = await $.post(getUserInput("url") + "/api/issues/fcc-project", test_data); assert.isObject(data); assert.nestedInclude(data, test_data); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: The `POST` request to `/api/issues/{projectname}` will return the created object, and must include all of the submitted fields. Excluded optional fields will be returned as empty strings. Additionally, include `created_on` (date/time), `updated_on` (date/time), `open` (boolean, `true` for open - default value, `false` for closed), and `_id`. testString: 'async getUserInput => { try { let test_data = { issue_title: "Faux Issue Title 2", issue_text: "Functional Test - Every field filled in", created_by: "fCC", assigned_to: "Chai and Mocha", }; const data = await $.post(getUserInput("url") + "/api/issues/fcc-project", test_data); assert.isObject(data); assert.nestedInclude(data, test_data); assert.property(data, "created_on"); assert.isNumber(Date.parse(data.created_on)); assert.property(data, "updated_on"); assert.isNumber(Date.parse(data.updated_on)); assert.property(data, "open"); assert.isBoolean(data.open); assert.isTrue(data.open); assert.property(data, "_id"); assert.isNotEmpty(data._id); assert.property(data, "status_text"); assert.isEmpty(data.status_text); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: If you send a `POST` request to `/api/issues/{projectname}` without the required fields, returned will be the error `{ error: 'required field(s) missing' }` testString: 'async getUserInput => { try { let test_data = { created_by: "fCC", }; const data = await $.post(getUserInput("url") + "/api/issues/fcc-project", { created_by: "fCC" }); assert.isObject(data); assert.property(data, "error"); assert.equal(data.error, "required field(s) missing"); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: You can send a `GET` request to `/api/issues/{projectname}` for an array of all issues for that specific `projectname`, with all the fields present for each issue. testString: 'async getUserInput => { try { let test_data = { issue_text: "Get Issues Test", created_by: "fCC" }; const url = getUserInput("url") + "/api/issues/get_issues_test_" + Date.now().toString().substring(7); const data1 = await $.post(url, Object.assign(test_data, {issue_title: "Faux Issue 1"})); assert.isObject(data1); const data2 = await $.post(url, Object.assign(test_data, {issue_title: "Faux Issue 2"})); assert.isObject(data2); const data3 = await $.post(url, Object.assign(test_data, {issue_title: "Faux Issue 3"})); assert.isObject(data3); const getIssues = await $.get(url); assert.isArray(getIssues); assert.lengthOf(getIssues, 3); let re = new RegExp("Faux Issue \\d"); getIssues.forEach(issue => { assert.property(issue, "issue_title"); assert.match(issue.issue_title,re); assert.property(issue,"issue_text"); assert.property(issue,"created_by"); assert.property(issue,"assigned_to"); assert.property(issue,"status_text"); assert.property(issue,"open"); assert.property(issue,"created_on"); assert.property(issue,"updated_on"); assert.property(issue,"_id"); }); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: You can send a `GET` request to `/api/issues/{projectname}` and filter the request by also passing along any field and value as a URL query (ie. `/api/issues/{project}?open=false`). You can pass one or more field/value pairs at once. testString: 'async getUserInput => { try { let test_data = { issue_title: "To be Filtered", issue_text: "Filter Issues Test" }; const url = getUserInput("url") + "/api/issues/get_issues_test_" + Date.now().toString().substring(7); const data1 = await $.post(url, Object.assign(test_data, {created_by: "Alice", assigned_to: "Bob"})); const data2 = await $.post(url, Object.assign(test_data, {created_by: "Alice", assigned_to: "Bob"})); const data3 = await $.post(url, Object.assign(test_data, {created_by: "Alice", assigned_to: "Eric"})); const data4 = await $.post(url, Object.assign(test_data, {created_by: "Carol", assigned_to: "Eric"})); const getSingle = await $.get(url + "?created_by=Alice"); assert.isArray(getSingle); assert.lengthOf(getSingle, 3); const getMultiple = await $.get(url + "?created_by=Alice&assigned_to=Bob"); assert.isArray(getMultiple); assert.lengthOf(getMultiple, 2); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: You can send a `PUT` request to `/api/issues/{projectname}` with an `_id` and one or more fields to update. On success, the `updated_on` field should be updated, and returned should be `{ result: 'successfully updated', '_id': _id }`. testString: 'async getUserInput => { try { let initialData = { issue_title: "Issue to be Updated", issue_text: "Functional Test - Put target", created_by: "fCC" }; const url = getUserInput("url") + "/api/issues/fcc-project"; const itemToUpdate = await $.post(url, initialData); const updateSucccess = await $.ajax({ url: url, type: "PUT", data: { "_id": itemToUpdate._id, issue_text: "New Issue Text" }}); assert.isObject(updateSucccess); assert.deepEqual(updateSucccess, { result: "successfully updated", "_id": itemToUpdate._id }); const getUpdatedId = await $.get(url + "?_id=" + itemToUpdate._id); assert.isArray(getUpdatedId); assert.isObject(getUpdatedId[0]); assert.isAbove(Date.parse(getUpdatedId[0].updated_on), Date.parse(getUpdatedId[0].created_on)); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: When the `PUT` request sent to `/api/issues/{projectname}` does not include an `_id`, the return value is `{ error: 'missing _id' }`. testString: 'async getUserInput => { try { const url = getUserInput("url") + "/api/issues/fcc-project"; const badUpdate = await $.ajax({url: url, type: "PUT"}); assert.isObject(badUpdate); assert.property(badUpdate, "error"); assert.equal(badUpdate.error, "missing _id"); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: When the `PUT` request sent to `/api/issues/{projectname}` does not include update fields, the return value is `{ error: 'no update field(s) sent', '_id': _id }`. On any other error, the return value is `{ error: 'could not update', '_id': _id }`. testString: 'async getUserInput => { try { const url = getUserInput("url") + "/api/issues/fcc-project"; const badUpdate = await $.ajax({ url: url, type: "PUT", data: {"_id": "5f665eb46e296f6b9b6a504d"} }); assert.deepEqual(badUpdate, { error: "no update field(s) sent", "_id": "5f665eb46e296f6b9b6a504d" }); const badIdUpdate = await $.ajax({ url: url, type: "PUT", data: { "_id": "5f665eb46e296f6b9b6a504d", issue_text: "New Issue Text" } }); assert.deepEqual(badIdUpdate, { error: "could not update", "_id": "5f665eb46e296f6b9b6a504d" }); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: You can send a `DELETE` request to `/api/issues/{projectname}` with an `_id` to delete an issue. If no `_id` is sent, the return value is `{ error: 'missing _id' }`. On success, the return value is `{ result: 'successfully deleted', '_id': _id }`. On failure, the return value is `{ error: 'could not delete', '_id': _id }`. testString: 'async getUserInput => { try { let initialData = { issue_title: "Issue to be Deleted", issue_text: "Functional Test - Delete target", created_by: "fCC" }; const url = getUserInput("url") + "/api/issues/fcc-project"; const itemToDelete = await $.post(url, initialData); assert.isObject(itemToDelete); const deleteSuccess = await $.ajax({url: url, type: "DELETE", data: { "_id": itemToDelete._id}}); assert.isObject(deleteSuccess); assert.deepEqual(deleteSuccess, { "result": "successfully deleted", "_id": itemToDelete._id }); const noId = await $.ajax({url: url, type: "DELETE"}); assert.isObject(noId); assert.deepEqual(noId, { "error": "missing _id" }); const badIdDelete = await $.ajax({url: url, type: "DELETE", data: {"_id": "5f665eb46e296f6b9b6a504d", issue_text: "New Issue Text" }}); assert.isObject(badIdDelete); assert.deepEqual(badIdDelete, { "error": "could not delete", "_id": "5f665eb46e296f6b9b6a504d" }); } catch(err) { throw new Error(err.responseText || err.message); } }' - text: All 14 functional tests are complete and passing. testString: 'async getUserInput => { try { const getTests = await $.get(getUserInput("url") + "/_api/get-tests" ); assert.isArray(getTests); assert.isAtLeast(getTests.length, 14, "At least 14 tests passed"); getTests.forEach(test => { assert.equal(test.state, "passed", "Test in Passed State"); assert.isAtLeast(test.assertions.length, 1, "At least one assertion per test"); }); } catch(err) { throw new Error(err.responseText || err.message); } }' ```
## Challenge Seed
## Solution
```js /** Backend challenges don't need solutions, because they would need to be tested against a full working project. Please check our contributing guidelines to learn more. */ ```