Unverified Commit 2e75b4ba authored by pom421's avatar pom421 Committed by GitHub
Browse files

Dev (#75)



* fix: better management of date pickers in declaration and statistics

* feat: add message on landing page from new table messages

* feat(test): add unit test for login page

* feat: add seed example for messages table

* feat(knex): add del for messages table

* feat(knex): seed for messages table in dev env

* feat: change color of welcome message

* feat: check that some hospitals can't do post mortem

* feat(stats): add "nb ETP" data

* feat: remove menu item not available for super admin

Update act can't be done by a super admin. A super admin can only delete an act because he has no hospital rattachment

* feat: better UX for remove buttons

* test: export test running with an endpoint in a folder e2e

* fix: prettier wasn't run on save anymore

* fix: currentUser was sometimes null. Fix this

Add a super admin check for some API endpoints which needed it

* fix(eslint)

* feat: add new statistics for deceased profile

* feat: redirect to login page for 401 errors detected on client side

* fix: eslint problems

Wrapper.getInitialProps must return an object, even in the redirect case

* refactor: handleAPIResponse manages now the redirect himself

Use a mock for testing authenticate API endpoint

* wording: replace emplois with ETP for coherence

* chore(deps): update socialgouv

* chore(deps): update all non-major dependencies

* refactor: remove unncessary lines in test

* chore(deps): update jest/babel-jest major versions

* chore(deps): update outdated libs

Refactor route "/index" in "/", since Next 9.5 seems to not accept "/index" anymore

* feat(statistics): exports data for thanato

fix the problem in Global tab, since the rewording of "Vivants (tous profils)" in "Vivants"

* fix(CI): the Sentry DSN was not well set

* fix(statistics): bug when click on clear button of the select. 

Remove clear button which was unncessary actually

* refactor(statistics): objToArray fn in component instead of page

* feat(statistics): add title in xls and better style
Co-authored-by: default avatarRenovate Bot <bot@renovateapp.com>
parent 4a74966e
Pipeline #60197 failed with stages
in 12 minutes and 18 seconds
......@@ -27,7 +27,7 @@ Build:
variables:
# these variables are needed at build time because embedded in the front
API_URL: "/api"
SENTRY_DSN: https://yyy@sentry.fabrique.social.gouv.fr/yyy
SENTRY_DSN: https://75f34cada95a4c189d69bc05e8aa324f@sentry.fabrique.social.gouv.fr/29
SENTRY_TOKEN: token
MATOMO_URL: https://matomo.io
MATOMO_SITE_ID: 4242
......
{
"eslint.enable": true,
"editor.formatOnSave": true,
"[javascript]": {
"editor.formatOnSave": false
},
"[javascriptreact]": {
"editor.formatOnSave": false
},
"[typescript]": {
"editor.formatOnSave": false
},
"[typescriptreact]": {
"editor.formatOnSave": false
},
"workbench.colorCustomizations": {
"statusBar.background": "#5fba7d"
},
......@@ -25,5 +12,6 @@
"css.lint.emptyRules": "ignore",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
},
"editor.formatOnSave": true
}
......@@ -2,7 +2,7 @@ version: "3"
services:
db:
restart: always
image: postgres:10.12
image: postgres:10.13
healthcheck:
test: ["CMD-SHELL", "pg_isready -U medle"]
interval: 5s
......
......@@ -7,7 +7,8 @@
"build": "node -r dotenv/config node_modules/next/dist/bin/next build",
"debug": "NODE_OPTIONS='--inspect' node -r dotenv/config node_modules/next/dist/bin/next dev",
"start": "node -r dotenv/config node_modules/next/dist/bin/next start",
"test": "node -r dotenv/config node_modules/jest-cli/bin/jest",
"test": "node -r dotenv/config node_modules/jest-cli/bin/jest --config ./src/__tests__/jest.config.js",
"test:e2e": "node -r dotenv/config node_modules/jest-cli/bin/jest --config ./src/__e2e__/jest.config.js --runInBand",
"lint": "eslint ./src",
"migrate:make": "node -r dotenv/config ./node_modules/knex/bin/cli migrate:make name_migration",
"migrate:latest": "node -r dotenv/config ./node_modules/knex/bin/cli migrate:latest",
......@@ -18,17 +19,17 @@
"seed:run:staging": "node -r dotenv/config ./node_modules/knex/bin/cli seed:run --env staging"
},
"dependencies": {
"@material-ui/core": "^4.9.12",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@mdx-js/loader": "^1.6.15",
"@next/mdx": "^9.3.6",
"@sentry/browser": "^5.15.5",
"@sentry/integrations": "^5.15.5",
"@sentry/node": "^5.15.5",
"@next/mdx": "^9.5.1",
"@sentry/browser": "^5.20.1",
"@sentry/integrations": "^5.20.1",
"@sentry/node": "^5.20.1",
"@socialgouv/bootstrap.core": "^0.2.1",
"@socialgouv/jours-feries": "^1.1.0",
"@socialgouv/jours-feries": "^2.0.0",
"bcryptjs": "^2.4.3",
"exceljs": "^3.9.0",
"exceljs": "^4.1.1",
"file-saver": "^2.0.2",
"isomorphic-unfetch": "^3.0.0",
"js-cookie": "^2.2.1",
......@@ -37,36 +38,39 @@
"knex-upsert": "^0.0.4",
"micro-cors": "^0.1.1",
"moize": "^5.4.7",
"moment": "^2.24.0",
"next": "^9.3.6",
"next-connect": "^0.6.6",
"moment": "^2.27.0",
"next": "^9.5.1",
"next-connect": "^0.8.1",
"next-cookies": "^2.0.3",
"pg": "^8.0.3",
"pg": "^8.3.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-hook-form": "^5.6.0",
"react-hook-form": "^6.2.0",
"react-select": "^3.1.0",
"react-switch": "^5.0.1",
"reactstrap": "^8.4.1",
"reactstrap": "^8.5.1",
"recharts": "^2.0.0-beta.6",
"remark-emoji": "^2.1.0",
"remark-images": "^2.0.0",
"styled-components": "^5.1.1",
"yup": "^0.28.5"
"yup": "^0.29.2"
},
"devDependencies": {
"@socialgouv/eslint-config-react": "^0.20.0",
"@socialgouv/eslint-config-recommended": "^0.20.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.0.4",
"babel-jest": "^25.5.1",
"@testing-library/dom": "^7.21.6",
"@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^10.4.7",
"@testing-library/user-event": "^12.0.17",
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint": "^7.5.0",
"faker": "^4.1.0",
"file-loader": "^6.0.0",
"jest-cli": "^25.5.4",
"msw": "^0.16.2",
"babel-jest": "^26.2.1",
"jest-cli": "^26.2.1",
"msw": "^0.20.0",
"next-compose-plugins": "^2.2.0",
"next-transpile-modules": "^3.3.0",
"next-transpile-modules": "^4.0.2",
"prettier": "^2.0.5",
"prop-types": "^15.7.2",
"react-test-renderer": "^16.13.1",
......
This folder gathers test with real database, reloaded between test.
For mocking version tests, see __tests__.
\ No newline at end of file
import { authenticate } from "../../../clients/authentication"
import { searchActs } from "../../../clients/acts"
import { authenticate } from "../../clients/authentication"
import { searchActs } from "../../clients/acts"
// const knexConfig = require("../knexfile")
// const knex = require("knex")(knexConfig)
// beforeAll(async () => {
// await knex.migrate.latest()
// await knex.seed.run()
// })
// afterAll(async () => {
// // Closing the DB connection allows Jest to exit successfully.
// await knex.destroy()
// })
const headersActUserTours = () => authenticate("acte@tours.fr", "test")
const headersActUserNantes = () => authenticate("acte@nantes.fr", "test")
......@@ -30,6 +43,12 @@ describe("clients for /acts ", () => {
}))
).toMatchInlineSnapshot(`
Array [
Object {
"hospital": 1,
"id": 8364,
"profile": "Personne décédée",
"pvNumber": "",
},
Object {
"hospital": 1,
"id": 8352,
......@@ -56,5 +75,36 @@ describe("clients for /acts ", () => {
},
]
`)
/*
`
Array [
Object {
"hospital": 1,
"id": 8352,
"profile": "Personne décédée",
"pvNumber": "2020/20063",
},
Object {
"hospital": 1,
"id": 8351,
"profile": "Personne décédée",
"pvNumber": "2020/20019",
},
Object {
"hospital": 1,
"id": 8350,
"profile": "Personne décédée",
"pvNumber": "70677/1539/2020",
},
Object {
"hospital": 1,
"id": 8349,
"profile": "Personne décédée",
"pvNumber": "2020/19887",
},
]
`
*/
})
})
import { authenticate } from "../../../clients/authentication"
import { createAct, searchActsByKey } from "../../../clients/acts"
import { authenticate } from "../../clients/authentication"
import { createAct, searchActsByKey } from "../../clients/acts"
const headersActUserTours = () => authenticate("acte@tours.fr", "test")
//const headersActUserNantes = () => authenticate("acte@nantes.fr", "test")
describe("/acts", () => {
it("should be possible to an act operator of Tours to add an act", async () => {
......
import { authenticate } from "../../../clients/authentication"
import { searchAskersFuzzy } from "../../../clients/askers"
import { authenticate } from "../../clients/authentication"
import { searchAskersFuzzy } from "../../clients/askers"
const headersActUserTours = () => authenticate("acte@tours.fr", "test")
......
import { authenticate } from "../../../clients/authentication"
import { findAllAttacks } from "../../../clients/attacks"
import { authenticate } from "../../clients/authentication"
import { findAllAttacks } from "../../clients/attacks"
const headersActUserTours = () => authenticate("acte@tours.fr", "test")
//const headersActUserNantes = () => authenticate("acte@nantes.fr", "test")
describe("/attacks", () => {
it("should return all attacks", async () => {
......
import { authenticate } from "../../../clients/authentication"
import { authenticate } from "../../clients/authentication"
describe("", () => {
import { redirectIfUnauthorized } from "../../utils/auth"
jest.mock("../../utils/auth")
import { logError } from "../../utils/logger"
jest.mock("../../utils/logger")
describe("check authentication API", () => {
it("should accept a good authentication", async () => {
const headersActUserTours = () => authenticate("acte@tours.fr", "test")
......@@ -41,9 +47,13 @@ describe("", () => {
}
`)
})
it("should not accept not correct email/password", async () => {
const headersActUserTours = () => authenticate("acte@tours.fr", "test2")
await expect(headersActUserTours).rejects.toMatchInlineSnapshot(`[APIError: Erreur d'authentification]`)
await expect(headersActUserTours).rejects.toMatchInlineSnapshot(`[Error: Authentication failed]`)
expect(redirectIfUnauthorized).toHaveBeenCalledTimes(1)
expect(logError).toHaveBeenCalledTimes(1)
})
})
import { authenticate } from "../../../clients/authentication"
import { searchHospitalsFuzzy } from "../../../clients/hospitals"
import { authenticate } from "../../clients/authentication"
import { searchHospitalsFuzzy } from "../../clients/hospitals"
const headersActUserTours = () => authenticate("acte@tours.fr", "test")
......
module.exports = {
// testRegex: "/.*\\.spec\\.js$",
}
// custom knexfile to ensure to use a test db
const { join } = require("path")
// WIP : how to manage a database, to reset and call an API endpoint in sync with the DB?
if (!process.env.E2E_JEST_DATABASE_URL) {
throw Error("No configured database for e2e tests. For production environement, please don't run e2e tests")
}
const knexConfig = {
client: "pg",
connection: process.env.E2E_JEST_DATABASE_URL,
pool: {
min: 0,
max: 2,
},
migrations: {
directory: join(__dirname, "../knex/migrations"),
},
seeds: {
directory: join(__dirname, "../knex/seeds/development"),
},
}
module.exports = knexConfig
This folder gathers test with mock function.
For test with real database, see __e2e__.
\ No newline at end of file
import React from "react"
import { rest } from "msw"
import { setupServer } from "msw/node"
import UserDetail from "../../../../../pages/administration/users/[id]"
import { API_URL, ADMIN_USERS_ENDPOINT } from "../../../../../config"
import { render, screen, fireEvent } from "@testing-library/react"
import { act, render, screen } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import * as nextRouter from "next/router"
import faker from "faker"
import UserDetail from "../../../../../pages/administration/users/[id]"
import { API_URL, USERS_ENDPOINT } from "../../../../../config"
describe("tests administration user", () => {
const server = setupServer(
rest.get(`${API_URL}${ADMIN_USERS_ENDPOINT}/:userId}`, (req, res, ctx) => {
rest.get(`${API_URL}${USERS_ENDPOINT}/42}`, (req, res, ctx) => {
const { userId } = req.params
return res(ctx.json({ data: "toto", userId }))
})
......@@ -18,47 +19,47 @@ describe("tests administration user", () => {
beforeAll(() => {
server.listen()
/* eslint-disable no-import-assign*/
nextRouter.useRouter = jest.fn()
nextRouter.useRouter.mockImplementation(() => ({ query: { id: faker.random.number() } }))
})
afterAll(() => server.close())
it("should not display Zone dangereuse for a new user", async () => {
nextRouter.useRouter = jest.fn()
nextRouter.useRouter.mockImplementation(() => ({ query: { id: "3" } }))
render(<UserDetail currentUser={{ role: "ADMIN_HOSPITAL" }} />)
// await expect(titleDangerousZone).rejects.toMatchInlineSnapshot()
expect(screen.queryByText("Zone dangereuse")).toBeNull()
expect(screen.queryByText("Retour à la liste")).toBeNull()
expect(screen.queryByText("Ajouter")).not.toBeNull()
expect(screen.queryByText("Zone dangereuse")).not.toBeInTheDocument()
expect(screen.queryByText("Retour à la liste")).not.toBeInTheDocument()
expect(screen.queryByText("Ajouter")).toBeInTheDocument()
// expect(rendered).toMatchInlineSnapshot()
})
it("should display Zone dangereuse for an existing user", async () => {
nextRouter.useRouter = jest.fn()
nextRouter.useRouter.mockImplementation(() => ({ query: { id: "3" } }))
render(<UserDetail initialUser={{ id: 42 }} currentUser={{ role: "ADMIN_HOSPITAL" }} />)
expect(screen.queryByText("Zone dangereuse")).not.toBeNull()
expect(screen.queryByText("Modifier")).not.toBeNull()
expect(screen.queryByText("Zone dangereuse")).toBeInTheDocument()
expect(screen.queryByText("Modifier")).toBeInTheDocument()
})
it("should display Retour à la liste after successful update", async () => {
nextRouter.useRouter = jest.fn()
nextRouter.useRouter.mockImplementation(() => ({ query: { id: "3" } }))
it("should display errors when clicking too early on Ajouter button", async () => {
render(<UserDetail currentUser={{ role: "ADMIN_HOSPITAL" }} />)
expect(screen.queryByText("Retour à la liste")).toBeNull()
expect(screen.queryByText("Retour à la liste")).not.toBeInTheDocument()
expect(screen.queryByText("Ajouter")).toBeInTheDocument()
expect(screen.queryByText("Ajouter")).not.toBeNull()
await act(async () => {
userEvent.click(screen.getByText("Ajouter"))
})
fireEvent.click(screen.getByText("Ajouter"))
await screen.findByText(/Le nom est obligatoire./i)
await screen.findByText(/Courriel a un format incorrect./i)
// const expectedButton = await screen.queryByText("Retour à la liste")
const expectedButton = await screen.queryByText("Retour à la liste")
// expect(expectedButton).not.toBeNull()
expect(expectedButton).not.toBeInTheDocument()
})
})
// const { join } = require("path")
//
module.exports = {
// rootDir: ".",
testRegex: "/.*\\.spec\\.js$",
//snapshotResolver: join(__dirname, "./snapshotResolver.js"),
//testEnvironmentOptions: require("./knexfile.ts"),
//testEnvironment: join(__dirname, "../..", require("../../package.json").main),
setupFilesAfterEnv: ["@testing-library/jest-dom/extend-expect"],
}
......@@ -17,12 +17,6 @@ describe("normalizeParams", () => {
currentUser: undefined,
}
// try {
// await normalizeParams(props, currentUser)
// } catch (err) {
// console.log("err", err)
// }
await expect(normalizeParams(props, currentUser)).resolves.toMatchInlineSnapshot(`
Object {
"asker": 3850,
......
......@@ -49,7 +49,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onClick={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root text-black-50"
focusable="false"
viewBox="0 0 24 24"
......@@ -139,7 +139,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root text-black-50"
focusable="false"
viewBox="0 0 24 24"
......@@ -161,7 +161,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
......@@ -184,7 +184,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
......@@ -207,7 +207,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
......@@ -230,7 +230,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
......@@ -244,7 +244,7 @@ exports[`should renders UserDetail unchanged 1`] = `
<br
className="jsx-1576199254"
/>
Emplois
ETP
</a>
<a
className="jsx-1576199254 list-group-item list-group-item-action unselected"
......@@ -253,7 +253,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
......@@ -276,7 +276,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
......@@ -299,7 +299,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
......@@ -341,7 +341,7 @@ exports[`should renders UserDetail unchanged 1`] = `
onMouseEnter={[Function]}
>
<svg
aria-hidden="true"
aria-hidden={true}
className="MuiSvgIcon-root"
focusable="false"
style={
......@@ -435,6 +435,7 @@ exports[`should renders UserDetail unchanged 1`] = `
className="col-sm-9"
>
<input
aria-invalid={false}
className="form-control"
id="lastName"
name="lastName"
......@@ -467,6 +468,7 @@ exports[`should renders UserDetail unchanged 1`] = `
className="col-sm-9"
>
<input
aria-invalid={false}
className="form-control"
id="email"
name="email"
......
import React from "react"
import LoginPage from "../../../pages/index"
jest.mock("../../../clients/messages")
import { render, screen } from "@testing-library/react"
import "@testing-library/jest-dom/extend-expect"
import LoginPage from "../../../pages/index"
test("it should display alerts", async () => {
render(<LoginPage message="Session terminée" welcomeMessage="Les ETP sont à remplir avant le mois de mars." />)
......
import React from "react"
import renderer from "react-test-renderer"
import * as nextRouter from "next/router"
import UserDetail from "../../../pages/administration/users/[id]"
import { SUPER_ADMIN } from "../../../utils/roles"
import * as nextRouter from "next/router"
// API dependency for pages's getInitialProps