mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-22 06:44:21 +00:00
📔 Complete?
This commit is contained in:
parent
ee19cfcd63
commit
2856fd0d57
8 changed files with 465 additions and 38 deletions
17
docs/source/3_dev/2_structure/2_frontend/1_techstack.rst
Normal file
17
docs/source/3_dev/2_structure/2_frontend/1_techstack.rst
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
Librerie e tecnologie utilizzate
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Sono elencate solo le principali librerie utilizzate; dipendenze e librerie minori non sono specificate, ma sono visibili all'interno del file ``yarn.lock``.
|
||||||
|
|
||||||
|
- I linguaggi di programmazione `JavaScript <https://developer.mozilla.org/en-US/docs/Web/JavaScript/About_JavaScript>`_ e `TypeScript <https://www.typescriptlang.org/>`_
|
||||||
|
- Il gestore di dipendenze `Yarn <https://yarnpkg.com/>`_
|
||||||
|
- La libreria grafica `Bluelib <https://github.com/Steffo99/bluelib>`_ (sviluppata come progetto personale nell'estate 2021)
|
||||||
|
- Il framework per interfacce grafiche `React <https://reactjs.org>`_
|
||||||
|
- Il router `Reach Router <https://reach.tech/router/>`_
|
||||||
|
- L'integrazione con React di Bluelib `bluelib-react <https://github.com/Steffo99/bluelib-react>`_ (sviluppata durante il tirocinio)
|
||||||
|
- Il componente React `react-markdown <https://github.com/remarkjs/react-markdown>`_
|
||||||
|
- Il framework per testing `Jest <https://jestjs.io/>`_
|
||||||
|
- Un fork personalizzato del client XHR `axios <https://github.com/axios/axios>`_
|
||||||
|
- Il webserver statico `serve <https://www.npmjs.com/package/serve>`_
|
23
docs/source/3_dev/2_structure/2_frontend/2_tree.rst
Normal file
23
docs/source/3_dev/2_structure/2_frontend/2_tree.rst
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
Struttura delle directory
|
||||||
|
-------------------------
|
||||||
|
.. default-domain:: js
|
||||||
|
|
||||||
|
Le directory di :mod:`@steffo45/sophon-frontend` sono strutturate nella seguente maniera:
|
||||||
|
|
||||||
|
src/components
|
||||||
|
Contiene i componenti React sia con le classi sia funzionali.
|
||||||
|
|
||||||
|
src/contexts
|
||||||
|
Contiene i contesti React creati con :func:`React.createContext`.
|
||||||
|
|
||||||
|
src/hooks
|
||||||
|
Contiene gli hook React personalizzati utilizzati nei componenti funzionali.
|
||||||
|
|
||||||
|
src/types
|
||||||
|
Contiene estensioni ai tipi base TypeScript, come ad esempio i tipi restituiti dalla web API del :ref:`modulo backend`.
|
||||||
|
|
||||||
|
src/utils
|
||||||
|
Contiene varie funzioni di utility.
|
||||||
|
|
||||||
|
public
|
||||||
|
Contiene i file statici da servire assieme all'app.
|
113
docs/source/3_dev/2_structure/2_frontend/3_resources.rst
Normal file
113
docs/source/3_dev/2_structure/2_frontend/3_resources.rst
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
Comunicazione con il server
|
||||||
|
---------------------------
|
||||||
|
.. default-domain:: js
|
||||||
|
|
||||||
|
|
||||||
|
Axios
|
||||||
|
^^^^^
|
||||||
|
|
||||||
|
Per effettuare richieste all'API web, si è deciso di utilizzare la libreria :mod:`axios`, in quanto permette di creare dei "client" personalizzabili con varie proprietà.
|
||||||
|
|
||||||
|
In particolare, si è scelto di forkarla, integrando anticipatamente una proposta di funzionalità che permette alle richieste di essere interrotte attraverso degli :class:`AbortController`.
|
||||||
|
|
||||||
|
|
||||||
|
Client personalizzati
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Per permettere all'utente di selezionare l'istanza da utilizzare e di comunicare con l'API con le proprie credenziali, si è scelto di creare client personalizzati partendo da due contesti.
|
||||||
|
|
||||||
|
All'interno di un contesto in cui è stata selezionata un'istanza (:data:`InstanceContext`), viene creato un client dal seguente hook:
|
||||||
|
|
||||||
|
.. function:: useInstanceAxios(config = {})
|
||||||
|
|
||||||
|
Questo hook specifica il ``baseURL`` del client Axios, impostandolo all'URL dell'istanza selezionata.
|
||||||
|
|
||||||
|
All'interno di un contesto in cui è stato effettuato l'accesso come utente (:data:`AuthorizationContext`), viene creato invece un client dal seguente hook:
|
||||||
|
|
||||||
|
.. function:: useAuthorizedAxios(config = {})
|
||||||
|
|
||||||
|
Questo hook specifica il valore dell'header ``Authorization`` da inviare in tutte le richieste effettuate a :samp:`Bearer {TOKEN}`, utilizzando il token ottenuto al momento dell'accesso.
|
||||||
|
|
||||||
|
|
||||||
|
Utilizzo di viewset
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Viene implementato un hook che si integra con i viewset di Django, fornendo un API semplificato per effettuare azioni su di essi.
|
||||||
|
|
||||||
|
.. function:: useViewSet(baseRoute)
|
||||||
|
|
||||||
|
Questo hook implementa tutte le azioni :py:mod:`rest_framework` di un viewset in lettura e scrittura.
|
||||||
|
|
||||||
|
Richiede di essere chiamato all'interno di un :data:`AuthorizationContext`.
|
||||||
|
|
||||||
|
.. function:: async list(config = {})
|
||||||
|
.. function:: async retrieve(pk, config = {})
|
||||||
|
.. function:: async create(config)
|
||||||
|
.. function:: async update(pk, config)
|
||||||
|
.. function:: async destroy(pk, config)
|
||||||
|
|
||||||
|
Viene inoltre fornito supporto per le azioni personalizzate.
|
||||||
|
|
||||||
|
.. function:: async command(config)
|
||||||
|
|
||||||
|
Permette azioni personalizzate su tutto il viewset.
|
||||||
|
|
||||||
|
.. function:: async action(config)
|
||||||
|
|
||||||
|
Permette azioni personalizzate su uno specifico oggetto del viewset.
|
||||||
|
|
||||||
|
|
||||||
|
Emulazione di viewset
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Viene creato un hook che tiene traccia degli oggetti restituiti da un determinato viewset, ed emula i risultati delle azioni effettuate, minimizzando i rerender e ottenendo una ottima user experience.
|
||||||
|
|
||||||
|
.. function:: useManagedViewSet(baseRoute, pkKey, refreshOnMount)
|
||||||
|
|
||||||
|
.. attribute:: viewset
|
||||||
|
|
||||||
|
Il viewset restituito da :func:`useViewSet`, utilizzato come interfaccia di basso livello per effettuare azioni.
|
||||||
|
|
||||||
|
.. attribute:: state
|
||||||
|
|
||||||
|
Lo stato del viewset, che tiene traccia degli oggetti e delle azioni in corso su di essi.
|
||||||
|
|
||||||
|
Gli oggetti all'interno di esso sono istanze di :class:`ManagedResource`, create usando wrapper di :func:`.update`, :func:`.destroy` e :func:`.action`, che permettono di modificare direttamente l'oggetto senza preoccuparsi dell'indice a cui si trova nell'array.
|
||||||
|
|
||||||
|
.. attribute:: dispatch
|
||||||
|
|
||||||
|
Riduttore che permette di alterare lo :attr:`.state`.
|
||||||
|
|
||||||
|
.. function:: async refresh()
|
||||||
|
|
||||||
|
Ricarica gli oggetti del viewset.
|
||||||
|
|
||||||
|
Viene chiamata automaticamente al primo render se ``refreshOnMount`` è :data:`True`.
|
||||||
|
|
||||||
|
.. function:: async create(data)
|
||||||
|
|
||||||
|
Crea un nuovo oggetto nel viewset con i dati specificati come argomento, e lo aggiunge allo stato se la richiesta va a buon fine.
|
||||||
|
|
||||||
|
.. function:: async command(method, cmd, data)
|
||||||
|
|
||||||
|
Esegue l'azione personalizzata ``cmd`` su tutto il viewset, utilizzando il metodo ``method`` e con i dati specificati in ``data``.
|
||||||
|
|
||||||
|
Se la richiesta va a buon fine, il valore restituito dal backend sostituisce nello stato le risorse dell'intero viewset.
|
||||||
|
|
||||||
|
.. function:: async update(index, data)
|
||||||
|
|
||||||
|
Modifica l'oggetto alla posizione ``index`` dell'array :attr:`.state` con i dati specificati in ``data``.
|
||||||
|
|
||||||
|
Se la richiesta va a buon fine, la modifica viene anche applicata all'interno di :attr:`.state`
|
||||||
|
|
||||||
|
.. function:: async destroy(index)
|
||||||
|
|
||||||
|
Elimina l'oggetto alla posizione ``index`` dell'array :attr:`.state`.
|
||||||
|
|
||||||
|
Se la richiesta va a buon fine, l'oggetto viene eliminato anche da :attr:`.state`.
|
||||||
|
|
||||||
|
.. function:: async action(index, method, act, data)
|
||||||
|
|
||||||
|
Esegue l'azione personalizzata ``act`` sull'oggetto alla posizione ``index`` dell'array :attr:`.state`, utilizzando il metodo ``method`` e con i dati specificati in ``data``.
|
||||||
|
|
||||||
|
Se la richiesta va a buon fine, il valore restituito dal backend sostituisce l'oggetto utilizzato in :attr:`.state`.
|
161
docs/source/3_dev/2_structure/2_frontend/4_contexts.rst
Normal file
161
docs/source/3_dev/2_structure/2_frontend/4_contexts.rst
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
Contesti innestati
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Per minimizzare i rerender, l'applicazione è organizzata a "contesti innestati".
|
||||||
|
|
||||||
|
|
||||||
|
I contesti
|
||||||
|
^^^^^^^^^^
|
||||||
|
|
||||||
|
Viene definito un contesto per ogni tipo di risorsa selezionabile nell'interfaccia.
|
||||||
|
|
||||||
|
Essi sono, in ordine dal più esterno al più interno:
|
||||||
|
|
||||||
|
#. :data:`InstanceContext` (:ref:`Istanza`)
|
||||||
|
#. :data:`AuthorizationContext` (:ref:`Utente`)
|
||||||
|
#. :data:`GroupContext` (:ref:`Gruppo di ricerca`)
|
||||||
|
#. :data:`ProjectContext` (:ref:`Progetto di ricerca`)
|
||||||
|
#. :data:`NotebookContext` (:ref:`Notebook`)
|
||||||
|
|
||||||
|
|
||||||
|
Contenuto dei contesti
|
||||||
|
""""""""""""""""""""""
|
||||||
|
|
||||||
|
Questi contesti possono avere tre tipi di valori: :data:`undefined` se ci si trova al di fuori del contesto, :data:`null` se non è stato selezionato alcun oggetto oppure **l'oggetto selezionato** se esso esiste.
|
||||||
|
|
||||||
|
|
||||||
|
URL contestuale
|
||||||
|
^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Si è definita la seguente struttura per gli URL del frontend di Sophon, in modo che essi identificassero universalmente una risorsa e che essi fossero human-readable.
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
/i/{ISTANZA}
|
||||||
|
/l/logged-in
|
||||||
|
/g/{GROUP_SLUG}
|
||||||
|
/p/{PROJECT_SLUG}
|
||||||
|
/n/{NOTEBOOK_SLUG}/
|
||||||
|
|
||||||
|
Ad esempio, l'URL per il notebook ``my-first-notebook`` dell'istanza demo di Sophon sarebbe:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
/i/https:api.prod.sophon.steffo.eu:
|
||||||
|
/l/logged-in
|
||||||
|
/g/my-first-group
|
||||||
|
/p/my-first-project
|
||||||
|
/n/my-first-notebook/
|
||||||
|
|
||||||
|
|
||||||
|
Parsing degli URL contestuali
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Viene definita una funzione in grado di comprendere gli URL contestuali:
|
||||||
|
|
||||||
|
.. function:: parsePath(path)
|
||||||
|
|
||||||
|
:param path: Il "path" da leggere.
|
||||||
|
:returns:
|
||||||
|
Un oggetto con le seguenti chiavi, dette "segmenti di percorso", le quali possono essere :data:`undefined` per indicare che non è stato selezionato un oggetto di quel tipo:
|
||||||
|
|
||||||
|
- ``instance``: l'URL dell'istanza da utilizzare, con caratteri speciali sostituiti da ``:``
|
||||||
|
- ``loggedIn``: :class:`Boolean`, se :data:`True` l'utente ha effettuato il login (come :ref:`Ospite` o :ref:`Utente`)
|
||||||
|
- ``researchGroup``: lo slug del :ref:`gruppo di ricerca` selezionato
|
||||||
|
- ``researchProject``: lo slug del :ref:`progetto di ricerca` selezionato
|
||||||
|
- ``notebook``: lo slug del :ref:`notebook` selezionato
|
||||||
|
|
||||||
|
Ad esempio, l'URL precedente restituirebbe il seguente oggetto se processato:
|
||||||
|
|
||||||
|
.. code-block:: js
|
||||||
|
|
||||||
|
{
|
||||||
|
"instance": "https:api.prod.sophon.steffo.eu:",
|
||||||
|
"loggedIn": True,
|
||||||
|
"researchGroup": "my-first-group",
|
||||||
|
"researchProject": "my-first-project",
|
||||||
|
"notebook": "my-first-notebook"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Routing basato sui contesti
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
I valori dei contesti vengono utilizzati per selezionare i componenti da mostrare all'utente nell'interfaccia grafica attraverso i seguenti componenti:
|
||||||
|
|
||||||
|
.. function:: ResourceRouter({selection, unselectedRoute, selectedRoute})
|
||||||
|
|
||||||
|
Componente che sceglie se renderizzare ``unselectedRoute`` o ``selectedRoute`` in base alla *nullità* o *non-nullità* di ``selection``.
|
||||||
|
|
||||||
|
.. function:: ViewSetRouter({viewSet, unselectedRoute, selectedRoute, pathSegment, pkKey})
|
||||||
|
|
||||||
|
Componente basato su :func:`ResourceRouter` che seleziona automaticamente l'elemento del viewset avente il valore del segmento di percorso ``pathSegment`` alla chiave ``pkKey``.
|
||||||
|
|
||||||
|
|
||||||
|
Esempio di utilizzo di ViewSetRouter
|
||||||
|
""""""""""""""""""""""""""""""""""""
|
||||||
|
|
||||||
|
.. function:: GroupRouter({...props})
|
||||||
|
|
||||||
|
Implementato come:
|
||||||
|
|
||||||
|
.. code-block:: tsx
|
||||||
|
|
||||||
|
<ViewSetRouter
|
||||||
|
{...props}
|
||||||
|
viewSet={useManagedViewSet<SophonResearchGroup>("/api/core/groups/", "slug")}
|
||||||
|
pathSegment={"researchGroup"}
|
||||||
|
pkKey={"slug"}
|
||||||
|
/>
|
||||||
|
|
||||||
|
|
||||||
|
Albero completo dei contesti
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
L'insieme di tutti i contesti è definito come componente :func:`App` nel modulo "principale" ``App.tsx``.
|
||||||
|
|
||||||
|
Se ne riassume la struttura in pseudocodice:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<InstanceContext>
|
||||||
|
<InstanceRouter>
|
||||||
|
unselected:
|
||||||
|
<InstanceSelect>
|
||||||
|
selected:
|
||||||
|
<AuthorizationContext>
|
||||||
|
<AuthorizationRouter>
|
||||||
|
unselected:
|
||||||
|
<UserLogin>
|
||||||
|
selected:
|
||||||
|
<GroupContext>
|
||||||
|
<GroupRouter>
|
||||||
|
unselected:
|
||||||
|
<GroupSelect>
|
||||||
|
selected:
|
||||||
|
<ProjectContext>
|
||||||
|
<ProjectRouter>
|
||||||
|
unselected:
|
||||||
|
<ProjectSelect>
|
||||||
|
selected:
|
||||||
|
<NotebookContext>
|
||||||
|
<NotebookRouter>
|
||||||
|
unselected:
|
||||||
|
<NotebookSelect>
|
||||||
|
selected:
|
||||||
|
<NotebookDetails>
|
||||||
|
|
||||||
|
|
||||||
|
Altri contesti
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Tema
|
||||||
|
""""
|
||||||
|
|
||||||
|
Il tema dell'istanza è implementato come uno speciale contesto globale :data:`ThemeContext` che riceve i dettagli dell'istanza a cui si è collegati dall':data:`InstanceContext`.
|
||||||
|
|
||||||
|
|
||||||
|
Cache
|
||||||
|
"""""
|
||||||
|
|
||||||
|
Viene salvato l'elenco di tutti i membri dell':ref:`istanza` in uno speciale contesto :data:`CacheContext` in modo da poter risolvere gli id degli utenti al loro username senza dover effettuare ulteriori richieste.
|
|
@ -8,28 +8,12 @@ Si è cercato di renderla più user-friendly possibile, cercando di comunicare p
|
||||||
|
|
||||||
È collocato all'interno del repository in ``/frontend``.
|
È collocato all'interno del repository in ``/frontend``.
|
||||||
|
|
||||||
|
Il modulo è formato dal package JavaScript :mod:`@steffo45/sophon-frontend`, che contiene tutti i componenti React che assemblati insieme formano l'intera interfaccia web.
|
||||||
|
|
||||||
Librerie e tecnologie utilizzate
|
.. toctree::
|
||||||
--------------------------------
|
:maxdepth: 1
|
||||||
|
|
||||||
.. note::
|
|
||||||
|
|
||||||
Sono elencate solo le principali librerie utilizzate; dipendenze e librerie minori non sono specificate, ma sono visibili all'interno del file ``yarn.lock``.
|
|
||||||
|
|
||||||
- I linguaggi di programmazione `JavaScript <https://developer.mozilla.org/en-US/docs/Web/JavaScript/About_JavaScript>`_ e `TypeScript <https://www.typescriptlang.org/>`_
|
|
||||||
- Il gestore di dipendenze `Yarn <https://yarnpkg.com/>`_
|
|
||||||
- La libreria grafica `Bluelib <https://github.com/Steffo99/bluelib>`_ (sviluppata come progetto personale nell'estate 2021)
|
|
||||||
- Il framework per interfacce grafiche `React <https://reactjs.org>`_
|
|
||||||
- Il router `Reach Router <https://reach.tech/router/>`_
|
|
||||||
- L'integrazione con React di Bluelib `bluelib-react <https://github.com/Steffo99/bluelib-react>`_ (sviluppata durante il tirocinio)
|
|
||||||
- Il componente React `react-markdown <https://github.com/remarkjs/react-markdown>`_
|
|
||||||
- Il framework per testing `Jest <https://jestjs.io/>`_
|
|
||||||
- Un fork personalizzato del client XHR `axios <https://github.com/axios/axios>`_
|
|
||||||
- Il webserver statico `serve <https://www.npmjs.com/package/serve>`_
|
|
||||||
|
|
||||||
|
|
||||||
Struttura del modulo
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Il modulo consiste nel package JavaScript :mod:`@steffo45/sophon-frontend`, che contiene tutti i componenti che assemblati insieme formano l'intera interfaccia web.
|
|
||||||
|
|
||||||
|
1_techstack
|
||||||
|
2_tree
|
||||||
|
3_resources
|
||||||
|
4_contexts
|
||||||
|
|
|
@ -1,3 +1,132 @@
|
||||||
Test effettuati
|
Test effettuati
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
Per motivi di tempo necessario, sono state selezionate solo alcune parti di codice su cui effettuare test.
|
||||||
|
|
||||||
|
|
||||||
|
Tutti i viewset di :py:mod:`sophon.core`
|
||||||
|
----------------------------------------
|
||||||
|
.. default-domain:: py
|
||||||
|
.. default-role:: py:obj
|
||||||
|
.. module:: sophon.core.tests
|
||||||
|
|
||||||
|
|
||||||
|
Test case generici
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Vengono definiti alcuni test case generici per facilitare le interazioni tra ``APITestCase`` e viewset.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
I nomi delle funzioni usano nomi con capitalizzazione inconsistente in quanto lo stesso modulo `unittest` non rispetta lo stile suggerito in :pep:`8`.
|
||||||
|
|
||||||
|
.. class:: BetterAPITestCase(APITestCase)
|
||||||
|
|
||||||
|
.. method:: as_user(self, username: str, password: str = None) -> typing.ContextManager[None]
|
||||||
|
|
||||||
|
Context manager che permette di effettuare richieste all'API come uno specifico utente, effettuando il logout quando sono state effettuate le richieste necessarie.
|
||||||
|
|
||||||
|
.. method:: assertData(self, data: ReturnDict, expected: dict)
|
||||||
|
|
||||||
|
Asserzione che permette di verificare che l'oggetto restituito da una richiesta all'API contenga almeno le chiavi e i valori contenuti nel dizionario ``expected``.
|
||||||
|
|
||||||
|
.. class:: ReadSophonTestCase(BetterAPITestCase, metaclass=abc.ABCMeta)
|
||||||
|
|
||||||
|
Classe **astratta** che implementa metodi per testare rapidamente le azioni di un `.views.ReadSophonViewSet`.
|
||||||
|
|
||||||
|
.. classmethod:: get_basename(cls) -> str
|
||||||
|
|
||||||
|
Metodo **astratto** che deve restituire il basename del viewset da testare.
|
||||||
|
|
||||||
|
.. classmethod:: get_url(cls, kind: str, *args, **kwargs) -> str
|
||||||
|
|
||||||
|
Metodo utilizzato dal test case per trovare gli URL ai quali possono essere effettuate le varie azioni.
|
||||||
|
|
||||||
|
I seguenti metodi permettono di effettuare azioni sul viewset:
|
||||||
|
|
||||||
|
.. method:: list(self) -> rest_framework.response.Response
|
||||||
|
.. method:: retrieve(self, pk) -> rest_framework.response.Response
|
||||||
|
.. method:: custom_list(self, method: str, action: str, data: dict = None) -> rest_framework.response.Response
|
||||||
|
.. method:: custom_detail(self, method: str, action: str, pk, data: dict = None) -> rest_framework.response.Response
|
||||||
|
|
||||||
|
I seguenti metodi asseriscono che una determinata azione con determinati parametri risponderà con il codice di stato ``code``, e restituiscono i dati contenuti nella risposta se l'azione è riuscita (``200 <= code < 300``)
|
||||||
|
|
||||||
|
.. method:: assertActionList(self, code: int = 200) -> typing.Optional[ReturnDict]
|
||||||
|
.. method:: assertActionRetrieve(self, pk, code: int = 200) -> typing.Optional[ReturnDict]
|
||||||
|
.. method:: assertActionCustomList(self, method: str, action: str, data: dict = None, code: int = 200) -> typing.Optional[ReturnDict]
|
||||||
|
.. method:: assertActionCustomDetail(self, method: str, action: str, pk, data: dict = None, code: int = 200) -> typing.Optional[ReturnDict]
|
||||||
|
|
||||||
|
|
||||||
|
.. class:: WriteSophonTestCase(ReadSophonTestCase, metaclass=abc.ABCMeta)
|
||||||
|
|
||||||
|
Classe **astratta** che estende `.ReadSophonTestCase` con le azioni di un `.views.WriteSophonViewSet`.
|
||||||
|
|
||||||
|
.. method:: create(self, data) -> rest_framework.response.Response
|
||||||
|
.. method:: update(self, pk, data) -> rest_framework.response.Response
|
||||||
|
.. method:: destroy(self, pk) -> rest_framework.response.Response
|
||||||
|
|
||||||
|
.. method:: assertActionCreate(self, data, code: int = 201) -> typing.Optional[ReturnDict]
|
||||||
|
.. method:: assertActionUpdate(self, pk, data, code: int = 200) -> typing.Optional[ReturnDict]
|
||||||
|
.. method:: assertActionDestroy(self, pk, code: int = 200) -> typing.Optional[ReturnDict]
|
||||||
|
|
||||||
|
|
||||||
|
Test case concreti
|
||||||
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Vengono testate tutte le view dell'app tramite `.BetterAPITestCase` e tutti i viewset dell'app tramite `.ReadSophonTestCase` e `WriteSophonTestCase`.
|
||||||
|
|
||||||
|
.. class:: UsersByIdTestCase(ReadSophonTestCase)
|
||||||
|
.. class:: UsersByUsernameTestCase(ReadSophonTestCase)
|
||||||
|
.. class:: ResearchGroupTestCase(WriteSophonTestCase)
|
||||||
|
.. class:: SophonInstanceDetailsTestCase(BetterAPITestCase)
|
||||||
|
|
||||||
|
|
||||||
|
Alcune interazioni di `sophon.notebooks`
|
||||||
|
----------------------------------------
|
||||||
|
.. default-domain:: py
|
||||||
|
.. default-role:: py:obj
|
||||||
|
.. module:: sophon.notebooks.tests
|
||||||
|
|
||||||
|
Vengono definiti alcuni test case per alcune interazioni dell'app `sophon.notebooks`.
|
||||||
|
|
||||||
|
.. class:: JupyterTestCase(TestCase)
|
||||||
|
|
||||||
|
Test case che testa la generazione dei token per Jupyter.
|
||||||
|
|
||||||
|
.. class:: ApacheTestCase(TestCase)
|
||||||
|
|
||||||
|
Test case che testa la conversione in `bytes` per la rubrica `dbm` del :ref:`modulo proxy`.
|
||||||
|
|
||||||
|
|
||||||
|
Alcune interazioni complicate del frontend
|
||||||
|
------------------------------------------
|
||||||
|
.. default-domain:: js
|
||||||
|
.. default-role:: js:class
|
||||||
|
|
||||||
|
Vengono infine definiti test case per alcune interazioni ritenute particolarmente complesse del frontend.
|
||||||
|
|
||||||
|
|
||||||
|
Encoding dell'URL dell'istanza nell'URL della pagina
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- encodes pathless URL
|
||||||
|
- encodes URL with port number
|
||||||
|
- encodes URL with simple path
|
||||||
|
- encodes URL with colon in path
|
||||||
|
- does not encode URL with ``%3A`` in path
|
||||||
|
- decodes pathless URL
|
||||||
|
- decodes URL with port number
|
||||||
|
- decodes URL with simple path
|
||||||
|
- decodes URL with colon in path
|
||||||
|
|
||||||
|
|
||||||
|
Parsing dei segmenti del path
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
- parses empty path
|
||||||
|
- parses instance path
|
||||||
|
- parses username path
|
||||||
|
- parses userid path
|
||||||
|
- parses research group path
|
||||||
|
- parses research project path
|
||||||
|
- parses research project path
|
||||||
|
|
|
@ -1,58 +1,58 @@
|
||||||
import { parsePath } from "./ParsePath"
|
import { parsePath } from "./ParsePath"
|
||||||
|
|
||||||
|
|
||||||
test("splits empty path", () => {
|
test("parses empty path", () => {
|
||||||
expect(
|
expect(
|
||||||
parsePath("/"),
|
parsePath("/"),
|
||||||
).toMatchObject(
|
).toMatchObject(
|
||||||
{}
|
{},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("splits instance path", () => {
|
test("parses instance path", () => {
|
||||||
expect(
|
expect(
|
||||||
parsePath("/i/https:api:sophon:steffo:eu:"),
|
parsePath("/i/https:api:sophon:steffo:eu:"),
|
||||||
).toMatchObject(
|
).toMatchObject(
|
||||||
{
|
{
|
||||||
instance: "https:api:sophon:steffo:eu:"
|
instance: "https:api:sophon:steffo:eu:",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("splits username path", () => {
|
test("parses username path", () => {
|
||||||
expect(
|
expect(
|
||||||
parsePath("/i/https:api:sophon:steffo:eu:/u/steffo"),
|
parsePath("/i/https:api:sophon:steffo:eu:/u/steffo"),
|
||||||
).toMatchObject(
|
).toMatchObject(
|
||||||
{
|
{
|
||||||
instance: "https:api:sophon:steffo:eu:",
|
instance: "https:api:sophon:steffo:eu:",
|
||||||
userName: "steffo",
|
userName: "steffo",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("splits userid path", () => {
|
test("parses userid path", () => {
|
||||||
expect(
|
expect(
|
||||||
parsePath("/i/https:api:sophon:steffo:eu:/u/1"),
|
parsePath("/i/https:api:sophon:steffo:eu:/u/1"),
|
||||||
).toMatchObject(
|
).toMatchObject(
|
||||||
{
|
{
|
||||||
instance: "https:api:sophon:steffo:eu:",
|
instance: "https:api:sophon:steffo:eu:",
|
||||||
userId: "1",
|
userId: "1",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("splits research group path", () => {
|
test("parses research group path", () => {
|
||||||
expect(
|
expect(
|
||||||
parsePath("/i/https:api:sophon:steffo:eu:/g/testers"),
|
parsePath("/i/https:api:sophon:steffo:eu:/g/testers"),
|
||||||
).toMatchObject(
|
).toMatchObject(
|
||||||
{
|
{
|
||||||
instance: "https:api:sophon:steffo:eu:",
|
instance: "https:api:sophon:steffo:eu:",
|
||||||
researchGroup: "testers",
|
researchGroup: "testers",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("splits research project path", () => {
|
test("parses research project path", () => {
|
||||||
expect(
|
expect(
|
||||||
parsePath("/i/https:api:sophon:steffo:eu:/g/testers/p/test"),
|
parsePath("/i/https:api:sophon:steffo:eu:/g/testers/p/test"),
|
||||||
).toMatchObject(
|
).toMatchObject(
|
||||||
|
@ -60,11 +60,11 @@ test("splits research project path", () => {
|
||||||
instance: "https:api:sophon:steffo:eu:",
|
instance: "https:api:sophon:steffo:eu:",
|
||||||
researchGroup: "testers",
|
researchGroup: "testers",
|
||||||
researchProject: "test",
|
researchProject: "test",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test("splits research project path", () => {
|
test("parses research project path", () => {
|
||||||
expect(
|
expect(
|
||||||
parsePath("/i/https:api:sophon:steffo:eu:/g/testers/p/test/n/testerino"),
|
parsePath("/i/https:api:sophon:steffo:eu:/g/testers/p/test/n/testerino"),
|
||||||
).toMatchObject(
|
).toMatchObject(
|
||||||
|
@ -73,6 +73,6 @@ test("splits research project path", () => {
|
||||||
researchGroup: "testers",
|
researchGroup: "testers",
|
||||||
researchProject: "test",
|
researchProject: "test",
|
||||||
notebook: "testerino",
|
notebook: "testerino",
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue