* fix/change-node-project-descriptions-to-all-use-same-template * fix: use correct boilerplate Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com> * fix: add url to link text Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com> * fix: add _blank for target attribute Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com> * fix: add missing single quote Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com> * fix: remove extra lines * fix: improved wording of description Co-authored-by: Shaun Hamilton <51722130+Sky020@users.noreply.github.com> * fix: move sentence to previous paragraph Co-authored-by: Shaun Hamilton <51722130+Sky020@users.noreply.github.com> * fix: move sentence to previous paragraph * fix: move sentence to previous paragraph Co-authored-by: Nicholas Carrigan (he/him) <nhcarrigan@gmail.com> Co-authored-by: Shaun Hamilton <51722130+Sky020@users.noreply.github.com>
294 lines
13 KiB
Markdown
294 lines
13 KiB
Markdown
---
|
||
id: 587d8249367417b2b2512c42
|
||
title: Issue Tracker
|
||
challengeType: 4
|
||
forumTopicId: 301569
|
||
---
|
||
|
||
## Description
|
||
<section id='description'>
|
||
Build a full stack JavaScript app that is functionally similar to this: <a href="https://issue-tracker.freecodecamp.rocks/" target="_blank">https://issue-tracker.freecodecamp.rocks/</a>. Working on this project will involve you writing your code using one of the following methods:
|
||
|
||
- Clone <a href='https://github.com/freeCodeCamp/boilerplate-project-issuetracker/' target='_blank'>this GitHub repo</a> and complete your project locally.
|
||
- Use <a href="https://repl.it/github/freeCodeCamp/boilerplate-project-issuetracker" target='_blank'>this repl.it starter project</a> 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.
|
||
</section>
|
||
|
||
## Instructions
|
||
<section id='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"
|
||
|
||
</section>
|
||
|
||
## Tests
|
||
<section id='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);
|
||
}
|
||
}'
|
||
```
|
||
|
||
</section>
|
||
|
||
## Challenge Seed
|
||
<section id='challengeSeed'>
|
||
|
||
</section>
|
||
|
||
## Solution
|
||
<section id='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.
|
||
*/
|
||
```
|
||
|
||
</section>
|