1
Fork 0
mirror of https://github.com/pds-nest/nest.git synced 2024-11-21 20:44:18 +00:00

Merge remote-tracking branch 'origin/main' into main

This commit is contained in:
Lorenzo Balugani 2021-05-13 11:40:25 +02:00
commit 396311aefd
8 changed files with 170 additions and 81 deletions

View file

@ -3,58 +3,101 @@ from flask.testing import Client
'''A file that contains tests of classes and methods for all the requests concerning an user.''' '''A file that contains tests of classes and methods for all the requests concerning an user.'''
class TestRepositoryGetAll:
def test_get_all_user_repositories(self, flask_client: Client, user_headers):
r = flask_client.get(f'/api/v1/repositories/', headers=user_headers,
json={'owner_id': 'utente_test@nest.com', 'isActive': False})
assert r.json["result"] == "success"
# assert r.json["data"]["owner"] == "utente_test@nest.com"
# assert r.json["data"]["isAdmin"] is not True
def test_get_all_admin_repositories(self, flask_client: Client, admin_headers):
r = flask_client.get(f'/api/v1/repositories/', headers=admin_headers,
json={'owner_id': 'admin@admin.com', 'isActive': False})
assert r.json["result"] == "success"
# assert r.json["data"]["owner"] == "admin@admin.com"
# assert r.json["data"]["isAdmin"] is True
class TestRepositoryAdd: class TestRepositoryAdd:
def test_for_success(self, flask_client: Client, user_headers): def test_for_success(self, flask_client: Client, user_headers):
r = flask_client.post(f'/api/v1/repositories/', headers=user_headers, json={ r = flask_client.post(f'/api/v1/repositories/', headers=user_headers, json={
'conditions': [ 'conditions': [
{ {
'content': 'PdS2021', 'content': 'PdS2021',
'id': 0, 'id': 0,
'type': 0 'type': 0
} }
], ],
'evaluation_mode': 0, 'evaluation_mode': 0,
'name': 'repo_test', 'name': 'repo_test',
'is_active': True 'is_active': True
}) })
assert r.status_code == 200
assert r.json["result"] == "success" assert r.json["result"] == "success"
assert r.json["data"]["is_active"] is True assert r.json["data"]["is_active"] is True
# non vengono passate le condizioni necessarie, in questo caso il nome della repository # non vengono passate le condizioni necessarie, in questo caso il nome della repository
def test_for_failure(self, flask_client: Client, user_headers): def test_no_name(self, flask_client: Client, user_headers):
r = flask_client.post(f'/api/v1/repositories/', headers=user_headers, json={ r = flask_client.post(f'/api/v1/repositories/', headers=user_headers, json={
'conditions': [ 'conditions': [
{ {
'content': 'PdS2021', 'content': 'PdS2021',
'id': 0, 'id': 0,
'type': 0 'type': 0
} }
], ],
'evaluation_mode': 0, 'evaluation_mode': 0,
'is_active': True 'is_active': True
}) })
assert r.status_code == 400
assert r.json["msg"] == "Missing arguments." assert r.json["msg"] == "Missing arguments."
assert r.json["result"] == "failure" assert r.json["result"] == "failure"
# viene passato un campo evaluation_mode con valore non previsto dall'enum
def test_wrong_evaluation_mode(self, flask_client: Client, user_headers):
r = flask_client.post(f'/api/v1/repositories/', headers=user_headers, json={
'conditions': [
{
'content': 'PdS2021',
'id': 0,
'type': 0
}
],
'evaluation_mode': 99,
'name': 'repo_test',
'is_active': True
})
assert r.status_code == 400
assert r.json["result"] == "failure"
# viene passato un campo type con valore non previsto dall'enum
def test_wrong_condition(self, flask_client: Client, user_headers):
r = flask_client.post(f'/api/v1/repositories/', headers=user_headers, json={
'conditions': [
{
'content': 'PdS2021',
'id': 0,
'type': 99
}
],
'evaluation_mode': 2,
'name': 'repo_test',
'is_active': True
})
assert r.status_code == 400
assert r.json["result"] == "failure"
class TestRepositoryGetAll:
def test_get_all_user_repositories(self, flask_client: Client, user_headers):
r = flask_client.get(f'/api/v1/repositories/', headers=user_headers,
json={'owner_id': 'utente_test@nest.com', 'isActive': False})
assert r.status_code == 200
assert r.json["result"] == "success"
def test_get_all_admin_repositories(self, flask_client: Client, admin_headers):
r = flask_client.get(f'/api/v1/repositories/', headers=admin_headers,
json={'owner_id': 'admin@admin.com', 'isActive': False})
assert r.status_code == 200
assert r.json["result"] == "success"
class TestRepositoryGet:
def test_get_existing_repository(self, flask_client: Client, user_headers):
r = flask_client.get(f'/api/v1/repositories/1', headers=user_headers)
assert r.status_code == 200
assert r.json["result"] == "success"
def test_get_non_existing_repository(self, flask_client: Client, admin_headers):
r = flask_client.get(f'/api/v1/repositories/99', headers=admin_headers)
assert r.status_code == 404
assert r.json["result"] == "failure"
''' '''
class TestUserDelete: class TestUserDelete:

View file

@ -1,3 +1,4 @@
import React from "react"
import Layout from "./components/interactive/Layout" import Layout from "./components/interactive/Layout"
import { BrowserRouter } from "react-router-dom" import { BrowserRouter } from "react-router-dom"
import GlobalTheme from "./components/providers/GlobalTheme" import GlobalTheme from "./components/providers/GlobalTheme"

View file

@ -3,41 +3,50 @@ import BoxFull from "../base/BoxFull"
import SummaryRepository from "./SummaryRepository" import SummaryRepository from "./SummaryRepository"
import { faFolderOpen } from "@fortawesome/free-solid-svg-icons" import { faFolderOpen } from "@fortawesome/free-solid-svg-icons"
import ContextUser from "../../contexts/ContextUser" import ContextUser from "../../contexts/ContextUser"
import Loading from "../base/Loading"
import BoxFullScrollable from "../base/BoxFullScrollable"
/** /**
* A {@link BoxFull} listing all the user's active repositories. * A {@link BoxFull} listing all the user's active repositories.
* *
* @param repositories - Array of repositories to display in the box. * @param repositories - Array of repositories to display in the box.
* @param refresh - Function that can be called to refresh the repositories list. * @param archiveRepository - Function to be called when archive is pressed on a repository summary.
* @param destroyRepository - Function to be called when delete is pressed on a repository summary.
* @param running - If an action is currently running.
* @param props - Additional props to pass to the box. * @param props - Additional props to pass to the box.
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */
export default function BoxRepositoriesActive({ repositories, refresh, ...props }) { export default function BoxRepositoriesActive({ repositories, archiveRepository, destroyRepository, running, ...props }) {
const { user } = useContext(ContextUser) const { user } = useContext(ContextUser)
let contents let contents
if(repositories.length > 0) { if(repositories === null) {
contents = <Loading/>
}
else if(repositories.length === 0) {
contents = <i>There's nothing here.</i>
}
else {
contents = repositories.map(repo => ( contents = repositories.map(repo => (
<SummaryRepository <SummaryRepository
key={repo["id"]} key={repo["id"]}
repo={repo} repo={repo}
icon={faFolderOpen} icon={faFolderOpen}
refresh={refresh} archiveSelf={() => archiveRepository(repo["id"])}
deleteSelf={() => destroyRepository(repo["id"])}
canArchive={true} canArchive={true}
canEdit={true} canEdit={true}
canDelete={repo["owner"]["username"] === user["username"]} canDelete={repo["owner"]["username"] === user["username"]}
running={running}
/> />
)) ))
} }
else {
contents = <i>There's nothing here.</i>
}
return ( return (
<BoxFull header={"Your active repositories"} {...props}> <BoxFullScrollable header={"Your active repositories"} {...props}>
{contents} {contents}
</BoxFull> </BoxFullScrollable>
) )
} }

View file

@ -1,43 +1,52 @@
import React, { useContext } from "react" import React, { useContext } from "react"
import BoxFull from "../base/BoxFull" import BoxFull from "../base/BoxFull"
import ContextUser from "../../contexts/ContextUser"
import SummaryRepository from "./SummaryRepository" import SummaryRepository from "./SummaryRepository"
import { faFolder } from "@fortawesome/free-solid-svg-icons" import { faFolderOpen } from "@fortawesome/free-solid-svg-icons"
import ContextUser from "../../contexts/ContextUser"
import Loading from "../base/Loading"
import BoxFullScrollable from "../base/BoxFullScrollable"
/** /**
* A {@link BoxFull} listing all the user's archived repositories. * A {@link BoxFull} listing all the user's archived repositories.
* *
* @param repositories - Array of repositories to display in the box. * @param repositories - Array of repositories to display in the box.
* @param refresh - Function that can be called to refresh the repositories list. * @param archiveRepository - Function to be called when archive is pressed on a repository summary.
* @param destroyRepository - Function to be called when delete is pressed on a repository summary.
* @param running - If an action is currently running.
* @param props - Additional props to pass to the box. * @param props - Additional props to pass to the box.
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */
export default function BoxRepositoriesArchived({ repositories, refresh, ...props }) { export default function BoxRepositoriesArchived({ repositories, archiveRepository, destroyRepository, running, ...props }) {
const { user } = useContext(ContextUser) const { user } = useContext(ContextUser)
let contents let contents
if(repositories.length > 0) { if(repositories === null) {
contents = <Loading/>
}
else if(repositories.length === 0) {
contents = <i>There's nothing here.</i>
}
else {
contents = repositories.map(repo => ( contents = repositories.map(repo => (
<SummaryRepository <SummaryRepository
key={repo["id"]} key={repo["id"]}
repo={repo} repo={repo}
icon={faFolder} icon={faFolderOpen}
refresh={refresh} archiveSelf={() => archiveRepository(repo["id"])}
deleteSelf={() => destroyRepository(repo["id"])}
canArchive={false} canArchive={false}
canEdit={false} canEdit={false}
canDelete={repo["owner"]["username"] === user["username"]} canDelete={repo["owner"]["username"] === user["username"]}
running={running}
/> />
)) ))
} }
else {
contents = <i>There's nothing here.</i>
}
return ( return (
<BoxFull header={"Your archived repositories"} {...props}> <BoxFullScrollable header={"Your active repositories"} {...props}>
{contents} {contents}
</BoxFull> </BoxFullScrollable>
) )
} }

View file

@ -13,20 +13,20 @@ import Summary from "../base/Summary"
* @param repo - The repository object. * @param repo - The repository object.
* @param refresh - Function that can be called to refresh the repositories list. * @param refresh - Function that can be called to refresh the repositories list.
* @param canDelete - If the Delete button should be displayed or not. * @param canDelete - If the Delete button should be displayed or not.
* @param deleteSelf - Function to call when the Delete button is pressed.
* @param canEdit - If the Edit button should be displayed or not. * @param canEdit - If the Edit button should be displayed or not.
* @param canArchive - If the Archive button should be displayed or not. * @param canArchive - If the Archive button should be displayed or not.
* @param archiveSelf - Function to call when the Archive button is pressed.
* @param running - If an action is currently running.
* @param className - Additional class(es) to be added to the outer box. * @param className - Additional class(es) to be added to the outer box.
* @param props - Additional props to pass to the outer box. * @param props - Additional props to pass to the outer box.
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */
export default function SummaryRepository( export default function SummaryRepository(
{ repo, refresh, canDelete, canEdit, canArchive, className, ...props }, { repo, refresh, canDelete, deleteSelf, canEdit, canArchive, archiveSelf, running, className, ...props },
) { ) {
const { fetchDataAuth } = useContext(ContextUser)
const history = useHistory() const history = useHistory()
const { fetchNow: archiveThis } = useBackend(fetchDataAuth, "PATCH", `/api/v1/repositories/${repo.id}`, { "close": true })
const { fetchNow: deletThis } = useBackend(fetchDataAuth, "DELETE", `/api/v1/repositories/${repo.id}`)
const onRepoClick = () => { const onRepoClick = () => {
history.push(`/repositories/${repo.id}`) history.push(`/repositories/${repo.id}`)
@ -36,22 +36,13 @@ export default function SummaryRepository(
history.push(`/repositories/${repo.id}/edit`) history.push(`/repositories/${repo.id}/edit`)
} }
const onArchiveClick = async () => {
await archiveThis()
await refresh()
}
const onDeleteClick = async () => {
await deletThis()
await refresh()
}
const buttons = <> const buttons = <>
{canDelete ? {canDelete ?
<Button <Button
color={"Red"} color={"Red"}
icon={faTrash} icon={faTrash}
onClick={onDeleteClick} onClick={deleteSelf}
disabled={running}
> >
Delete Delete
</Button> </Button>
@ -61,6 +52,7 @@ export default function SummaryRepository(
color={"Yellow"} color={"Yellow"}
icon={faPencilAlt} icon={faPencilAlt}
onClick={onEditClick} onClick={onEditClick}
disabled={running}
> >
Edit Edit
</Button> </Button>
@ -69,7 +61,8 @@ export default function SummaryRepository(
<Button <Button
color={"Grey"} color={"Grey"}
icon={faArchive} icon={faArchive}
onClick={onArchiveClick} onClick={archiveSelf}
disabled={running}
> >
{"Archive"} {"Archive"}
</Button> </Button>

View file

@ -127,6 +127,25 @@ export default function useBackendViewset(resourcesPath, pkName) {
[apiList], [apiList],
) )
const refreshResource = useCallback(
async (pk) => {
try {
const refreshedResource = await apiRetrieve(pk)
setResources(resources => resources.map(resource => {
if(resource[pkName] === pk) {
return refreshedResource
}
return resource
}))
}
catch(e) {
return { error: e }
}
return {}
},
[apiRetrieve, pkName]
)
const createResource = useCallback( const createResource = useCallback(
async (data) => { async (data) => {
try { try {
@ -191,12 +210,14 @@ export default function useBackendViewset(resourcesPath, pkName) {
resources, resources,
running, running,
loaded, loaded,
apiRequest,
apiList, apiList,
apiRetrieve, apiRetrieve,
apiCreate, apiCreate,
apiEdit, apiEdit,
apiDestroy, apiDestroy,
refreshResources, refreshResources,
refreshResource,
createResource, createResource,
editResource, editResource,
destroyResource, destroyResource,

View file

@ -1,32 +1,44 @@
import React, { useContext } from "react" import React, { useCallback, useContext } from "react"
import Style from "./PageRepositories.module.css" import Style from "./PageRepositories.module.css"
import classNames from "classnames" import classNames from "classnames"
import BoxRepositoriesActive from "../components/interactive/BoxRepositoriesActive" import BoxRepositoriesActive from "../components/interactive/BoxRepositoriesActive"
import BoxRepositoriesArchived from "../components/interactive/BoxRepositoriesArchived" import BoxRepositoriesArchived from "../components/interactive/BoxRepositoriesArchived"
import useBackendImmediately from "../hooks/useBackendImmediately" import useBackendViewset from "../hooks/useBackendViewset"
import ContextUser from "../contexts/ContextUser"
import renderContents from "../utils/renderContents"
export default function PageRepositories({ children, className, ...props }) { export default function PageRepositories({ children, className, ...props }) {
const { fetchDataAuth } = useContext(ContextUser) const bv = useBackendViewset("/api/v1/repositories/", "id")
const repositoryRequest = useBackendImmediately(fetchDataAuth, "GET", "/api/v1/repositories/")
const contents = renderContents( const archiveRepository = useCallback(
repositoryRequest, async (pk) => {
data => { try {
const repositories = [...data["owner"], ...data["spectator"]] await bv.apiRequest("PATCH", `/api/v1/repositories/${pk}`, {
const active = repositories.filter(r => r.is_active) "close": true,
const archived = repositories.filter(r => !r.is_active) })
return <> await bv.refreshResource(pk)
<BoxRepositoriesActive repositories={active} refresh={repositoryRequest.fetchNow}/> }
<BoxRepositoriesArchived repositories={archived} refresh={repositoryRequest.fetchNow}/> catch(e) {
</> return { error: e }
}
return {}
}, },
[bv.apiRequest, bv.refreshResource]
) )
return ( return (
<div className={classNames(Style.PageRepositories, className)} {...props}> <div className={classNames(Style.PageRepositories, className)} {...props}>
{contents} <BoxRepositoriesActive
repositories={bv.loaded ? bv.resources.filter(r => r.is_active) : null}
archiveRepository={archiveRepository}
destroyRepository={bv.destroyResource}
running={bv.running}
/>
<BoxRepositoriesArchived
repositories={bv.loaded ? bv.resources.filter(r => !r.is_active) : null}
archiveRepository={archiveRepository}
destroyRepository={bv.destroyResource}
running={bv.running}
/>
</div> </div>
) )
} }

View file

@ -1,3 +1,4 @@
import React from "react"
import Loading from "../components/base/Loading" import Loading from "../components/base/Loading"
import BoxAlert from "../components/base/BoxAlert" import BoxAlert from "../components/base/BoxAlert"
import Starting from "../components/base/Starting" import Starting from "../components/base/Starting"