Browse Source

Dev 3747 new exercise about handling different http request methods (#1624)

* docs(piscine-js): add description for the subject uninvited

* docs(uninvited.mjs): modify name in example

* docs(uninvited): modify example

* test(uninvited_test): add preliminary tests

* fix(uninvited_test): export variable tests

* chore(uninvited): run prettier

* test(uninvited): write test to check for invalid status code

* test(uninvited): write test to check for invalid content type

* chore(uninvited): prettier

* test(uninvited): add test to check if server failed

* test(uninvited): export tests

* test(uninvited): freeze tests to avoid tampering

* test(uninvited): remove unused import

* test(uninvited): remove unused parameter

* test(uninvited): catch rejected promise

* test(uninvited): return boolean with result of creating file

* test(uninvited): fail if trying to write to an existing file, add flag

* test(uninvited): write test to check file is created

* test(uninvited): fix wrong path

* test(uninvited): rename function for accuracy

* test(uninvited): dry code

* test(uninvited): rename functions for consistency

* test(uninvited): test request body on success

* test(uninvited): kill server before if statement

* docs(uninvited): rephrase for conciseness

* debug(uninvited): Working up to test 4

* test(uninvited): working tests for the subject

* chore(uninvited): prettier

* chore(uninvited): clean-up, remove extra lines and redundant parameter

* tests(uninvited): replace undefined by constant value

* test(uninvited): add test to check content of file

* chore(uninvited): remove redundant assignment

* fix(uninvited): remove yarn.lock

* chore(uninvited): remove console log

* docs(uninvited): specify what to do when the file already exists

* fix(uninvited): revert gitignore

* fix(test): restore unused import

* docs(uninvited): add link on how to read POST data from a request

* docs(uninvited): remove mention to alcohol

* test(uninvited): remove unused import

* fix(gitignore): add new line at end of file

* docs(uninvited): make example more neutral
pull/1687/head
eslopfer 1 year ago committed by GitHub
parent
commit
2cc7a8a432
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 197
      js/tests/uninvited_test.mjs
  2. 43
      subjects/uninvited/README.md

197
js/tests/uninvited_test.mjs

@ -0,0 +1,197 @@
import { once } from 'node:events'
import { spawn } from 'node:child_process'
import { mkdir, writeFile, chmod } from 'fs/promises'
import { join } from 'path'
import fs from 'node:fs/promises'
export const tests = []
const fetch = _fetch // to redefine the real fetch
const port = 5000
export const setup = async ({ randStr }) => {
const dir = '.'
await mkdir(`${dir}/guests`, { recursive: true })
const randomName = randStr()
const createFilesIn = ({ files, dirPath }) => {
Promise.all(
files.map(([fileName, content]) =>
writeFile(`${dirPath}/${fileName}`, JSON.stringify(content), {
flag: 'wx',
}),
),
).catch(reason => console.log(reason))
return true
}
const sendRequest = async (path, options) => {
const response = await fetch(`http://localhost:${port}${path}`, options)
const { status } = response
const headers = Object.fromEntries(response.headers)
let body = ''
try {
body = await response.json()
} catch (err) {
body = err
}
return { status, body, headers }
}
const startServer = async path => {
const server = spawn('node', [`${path}`])
const message = await Promise.race([
once(server.stdout, 'data'),
Promise.race([
once(server.stderr, 'data').then(String).then(Error),
once(server, 'error'),
]).then(result => Promise.reject(result)),
])
return { server, message }
}
return { tmpPath: dir, createFilesIn, randomName, sendRequest, startServer }
}
const testServerRunning = async ({ path, ctx }) => {
const { server, message } = await ctx.startServer(path)
server.kill()
return message[0].toString().includes(port)
}
const testRightStatusCode = async ({ path, ctx, randStr }) => {
const { server } = await ctx.startServer(path)
const { status } = await ctx.sendRequest(`/${ctx.randomName}`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: randStr(),
})
server.kill()
if (status != 201) return false
return true
}
const testRightContentType = async ({ path, ctx, randStr }) => {
const { server } = await ctx.startServer(path)
const { headers } = await ctx.sendRequest(`/${ctx.randomName}`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: randStr(),
})
server.kill()
if (headers['content-type'] != 'application/json') return false
return true
}
const testServerFail = async ({ path, eq, ctx, randStr }) => {
const { server } = await ctx.startServer(path)
await chmod(`${ctx.tmpPath}/guests`, 0).catch(reason => console.log(reason))
const { status, body, headers } = await ctx.sendRequest(
`/${ctx.randomName}`,
{
method: 'POST',
body: randStr(),
},
)
await chmod(`${ctx.tmpPath}/guests`, 0o777)
server.kill()
return eq(
{
status: status,
body: body,
'content-type': headers['content-type'],
},
{
status: 500,
body: { error: 'server failed' },
'content-type': 'application/json',
},
)
}
const testFileCreated = async ({ path, ctx, randStr }) => {
const { server } = await ctx.startServer(path)
const randomName = randStr()
await ctx.sendRequest(`/${randomName}`, {
body: randStr(),
method: 'POST',
})
const dirName = 'guests'
const dirPath = join(ctx.tmpPath, dirName)
let accessWorked = true
server.kill()
await fs
.access(`${dirPath}/${randomName}.json`, fs.constants.F_OK)
.catch(reason => {
accessWorked = false
console.log(reason)
})
return accessWorked
}
const testFileContent = async ({ path, ctx, randStr }) => {
const { server } = await ctx.startServer(path)
const randomName = randStr()
const body = randStr()
await ctx.sendRequest(`/${randomName}`, {
body: body,
method: 'POST',
})
const dirName = 'guests'
const dirPath = join(ctx.tmpPath, dirName)
server.kill()
let content = ''
await fs
.readFile(`./${dirPath}/${randomName}.json`, 'utf8', (err, data) => {
if (err) {
console.error(err)
return 'error when reading file'
}
return data
})
.then(data => {
if (data === 'error when reading file') return
content = data
})
return body === content
}
const testBodyOnSuccess = async ({ path, ctx, eq, randStr }) => {
const { server } = await ctx.startServer(path)
const randomBody = { message: randStr() }
const { body } = await ctx.sendRequest(`/${ctx.randomName}`, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify(randomBody),
})
server.kill()
return eq(
{
body: body,
},
{
body: randomBody,
},
)
}
tests.push(
testServerRunning,
testRightStatusCode,
testRightContentType,
testBodyOnSuccess,
testFileCreated,
testFileContent,
testServerFail,
)
Object.freeze(tests)

43
subjects/uninvited/README.md

@ -0,0 +1,43 @@
## uninvited
### Instructions
When you started to organize the party you thought it would be easier. Your friend who started helping on the last exercise, raised a question that you didn't think about before. What would happen if people showed up with a plus-one? Or a plus-three?
Oh no! You didn't take into account people uninvited who might come with your guests.
For now, what your friend suggested is to call the guests and try to find out who would come with company.
Create an `uninvited.mjs` program that will open a server to remotely not just access, but also update the list. It will need to handle http `POST` requests to add new guests.
Here below are your program/server's expected behaviors:
- It has to listen on port `5000` and it will have to print a simple message on the console, specifying the listening port.
- Its HTTP response should contain a coherent status code depending on the handling of the received HTTP request. More specifically, your server should be able to respond with the following status codes: `201` and `500`.
- The responses will always be JSON and this information should be explicit in the HTTP response.
- For each http `POST` request, your program should create the corresponding JSON file and store the contents of the body, and then provide the content as JSON in the HTTP response, if possible. If the file already exists, it should replace it.
- If for any reason the server fails, the response should be an object with a key `error` and its value `server failed`.
### Example
To test your program, you should be able to expect the following behaviour once your program is up and running.
```shell
curl -X POST localhost:5000/Ronaldinho -H "Content-Type: application/json" -d '{"answer": "yes", "drink": "guarana", "food": "chicken stroganoff"}'
{
"answer": "yes",
"drink": "guarana",
"food": "chicken stroganoff"
}
```
### Notions
- [HTTP protocol](https://developer.mozilla.org/en-US/docs/Web/HTTP)
- [HTTP Status Codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
- [Node http package: createServer](https://nodejs.org/en/knowledge/HTTP/servers/how-to-create-a-HTTP-server/)
- [How to read POST data](https://nodejs.org/en/knowledge/HTTP/servers/how-to-read-POST-data/)
### Provided files
Download [`guests.zip`](https://assets.01-edu.org/tell-me-how-many/guests.zip) to have at your disposal the `guests` directory containing the files with the guests information. You must save it in your `uninvited` exercise directory to test your program on it.
Loading…
Cancel
Save