Files

370 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: 587d8249367417b2b2512c42
title: 问题跟踪器
challengeType: 4
forumTopicId: 301569
dashedName: issue-tracker
---
# --description--
构建一个 JavaScript 的全栈应用,在功能上与这个应用相似: <https://issue-tracker.freecodecamp.rocks/>。 可以采用下面的任意一种方式完成这个挑战:
- 克隆 [GitHub 仓库](https://github.com/freeCodeCamp/boilerplate-project-issuetracker/) 并在本地完成你的项目。
- 使用[我们的 Replit 初始化项目](https://replit.com/github/freeCodeCamp/boilerplate-project-issuetracker)来完成你的项目。
- 使用一个你喜欢的站点生成器来完成项目。 需要确定包含了我们 GitHub 仓库的所有文件。
完成本项目后,请将一个正常运行的 demo项目演示托管在可以公开访问的平台。 然后在 `Solution Link` 框中提交你的项目 URL。 此外,还可以将项目的源码提交到 `GitHub Link` 中。
# --instructions--
-`/routes/api.js` 中完成必要的路由
-`tests/2_functional-tests.js` 中创建所有的功能测试
- 复制 `sample.env` 文件到 `.env` 并按需设置变量
- 要运行测试,在 `.env` 文件中取消注释 `NODE_ENV=test`
- 使用 `npm run test` 命令,在 console 运行测试。 按 Ctrl+Shift+P在 Mac 上是 Cmd+Shift+P并输入“open shell”打开 Replit 控制台。
`tests/2_functional-tests.js` 中编写下以下测试:
- 用所有字段创建 issuePOST 请求到 `/api/issues/{project}`
- 用必填字段创建 issuePOST 请求到 `/api/issues/{project}`
- 用缺失必填字段创建 issuePOST 请求到 `/api/issues/{project}`
- 查看 project 里的 issueGET 请求到 `/api/issues/{project}`
- 用 filter 过滤 project 里的 issueGET 请求到 `/api/issues/{project}`
- 用多个 filter 过滤 project 里的 issueGET 请求到 `/api/issues/{project}`
- 更新 issue 里的一个字段PUT 请求到 `/api/issues/{project}`
- 更新 issue 里的多个字段PUT 请求到 `/api/issues/{project}`
- 在缺少 `_id` 字段的情况下更新 issue: PUT 请求到 `/api/issues/{project}`
- 在没有字段更新的情况下调用更新PUT 请求到 `/api/issues/{project}`
- 传入一个无效的的 `_id` 来调用更新PUT 请求到 `/api/issues/{project}`
- 删除一个 issueDELETE 请求到 `/api/issues/{project}`
- 传入一个无效的的 `_id` 来调用删除DELETE 请求到 `/api/issues/{project}`
- 在缺失 `_id` 的情况下来调用删除DELETE 请求到 `/api/issues/{project}`
# --hints--
提交自己的项目,而不是示例的 URL。
```js
(getUserInput) => {
assert(!/.*\/issue-tracker\.freecodecamp\.rocks/.test(getUserInput('url')));
};
```
可以发送 `POST` 请求到 `/api/issues/{projectname}`,表单数据包含必填字段 `issue_title``issue_text``created_by` 和可选字段 `assigned_to` 以及 `status_text`
```js
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);
}
};
```
`POST` 请求到 `/api/issues/{projectname}` 将返回创建的对象,必须包含所有提交的全部字段。 如果没有填选填字段将作为空字符串返回. 此外,包含 `created_on` (日期/时间)、 `updated_on` (日期/时间)、 `open` (布尔型) `true` 用于打开 - 默认值, `false` 用于关闭, `_id`
```js
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);
}
};
```
如果发送一个 `POST` 请求到 `/api/issues/{projectname}` 且缺少必填字段,会返回错误 `{ error: 'required field(s) missing' }`
```js
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);
}
};
```
可以发送 `GET` 请求到 `/api/issues/{projectname}` 请求所有指定 `projectname` 的 issues 数组,会展示每个 issue 的所有字段。
```js
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);
}
};
```
可以发送 `GET` 请求到 `/api/issues/{projectname}` 通过 URL 查询传入字段名和值过滤请求(如, `/api/issues/{project}?open=false`)。 你可以一次通过一个或多个字段/值对。
```js
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);
}
};
```
你可以发送一个 `PUT` 请求到 `/api/issues/{projectname}` 带有一个 `_id` 以及一个或多个字段进行更新。 成功后, `updated_on` field 应该被更新,返回的应该是 `{ result: 'successfully updated', '_id': _id }`
```js
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);
}
};
```
`PUT` 请求发送给 `/api/issues/{projectname}` 的请求体不包含 `_id` 时应返回`{ error: 'missing _id' }`
```js
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);
}
};
```
`PUT` 请求发送给 `/api/issues/{projectname}` 的请求体不包含任何更新的字段,应返回 `{ error: 'no update field(s) sent', '_id': _id }`。 在任何其他错误,应返回 `{ error: 'could not update', '_id': _id }`
```js
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);
}
};
```
你可以发送一个 `DELETE` 请求到 `/api/issues/{projectname}` 带有一个 `_id` 来删除 issue。 如果没有发送 `_id` ,返回值为 `{ error: 'missing _id' }`。 成功后,返回值为 `{ result: 'successfully deleted', '_id': _id }`。 失败时,返回值为 `{ error: 'could not delete', '_id': _id }`
```js
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);
}
};
```
所有 14 项功能测试都已完成并通过。
```js
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);
}
};
```
# --solutions--
```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.
*/
```