# Tests e2e

## Tests end-to-end

Idéalement on aura la possibilité de tester notre API, et pas juste des modules au sein de notre API.

C'est-à-dire, on envoie des requêtes HTTP et on évalue les réponses.

Cela veut dire qu'il faut lancer notre serveur et créer de requêtes HTTPS et recevoir des réponses. Comment faire ?

Grâce à une conception intelligente, nous avons créé deux fonctions `StartServer` et `StopServer` dans `src/servce_manager.ts` qui nous permettent de démarrer et d'arrêter le serveur.

Avec cette structure on peut facilement lancer un serveur à partir de nos tests.

## Outil pour lancer le serveur

Nous créons un outil qui permet de lancer et arrêter le serveur dans `test/utility/TestServer.ts` :

```ts
import { Server } from 'http';
import { StartServer, StopServer } from '../../src/server_manager';

export class TestServer {

  private static _server: Server|undefined;

  public static async Start() {    
    if (process.env.START_HOST) {
      TestServer._server = await StartServer();
    }
  }

  public static async Stop() {
    await StopServer(TestServer._server);
  }

}
```

Ce script lance simplement le serveur et lui garde sa référence.

Vous remarquerez la variable `START_HOST`. En effet, nous allons lancer le serveur seulement si cette variable est présente. Pourquoi ?

* Pour nos tests locaux, nous allons affecter une valeur à `START_HOST` pour lancer un serveur.
* A terme, nous allons compiler un *artifact* de notre API en forme de container Docker, qui sera lancé tout seul. On aimerait lancer nos tests e2e qui vont plutôt envoyer les requêtes à ce container. Dans ce cas, pas besoin de lancer notre serveur en local.

Du coup, nous allons ajouter quelques valeurs à notre fichier `test/.env.test` :

```sh
# API 
PORT=21011
API_HOST=http://localhost:21011

# TESTING
START_HOST=true
```

Cela pour indiquer qu'il faut lancer le serveur. Aussi, nous précisons le port d'écoute du serveur, ainsi que l'adresse HTTP où on peut trouver notre API.

### Le test e2e

Ensuite, dans un test e2e pour un utilisateur `/test/e2e/suites/User.test.ts`, on peut le lancer et fermer dans les *hooks* `before` et `after` :

```ts
import { DB } from '@orm/DB';
import axios from 'axios';
import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';
import { describe } from 'mocha';
import { RootDB } from '../../utility/RootDB';
import { TestServer } from '../../utility/TestServer';

chai.use(chaiAsPromised);

describe("User CRUD", function () {
  
  before(async function() {
    // Vider la base de données de test
    await RootDB.Reset();
    // Lancer le serveur
    await TestServer.Start();
  });

  after(async function() {
    // Forcer la fermeture de la base de données
    await DB.Close();
    // Arreter le serveur
    await TestServer.Stop();    
  });

  it("Create a new user", async function () {
    const result = await axios.put(process.env.API_HOST + '/user', 
      {
        familyName: "Glass",
        givenName: "Kevin",
        email: "kevin@nguni.fr",
        balance: 0
      }, 
      {
        headers: {
          Authorization: "Bearer INSERT HERE"
        }
      }
    );

    chai.expect(result.status).to.equal(200);
    chai.expect(result.data.id).to.equal(1);    
  });

  it("Create the same user twice returns an error", async function () {

    const response = await axios.put(process.env.API_HOST + '/user', 
      {
        familyName: "Glass",
        givenName: "Kevin",
        email: "kevin@nguni.fr",
        balance: 0
      }, 
      {        
        headers: {
          Authorization: "Bearer INSERT HERE"
        },
        validateStatus: (status) => { return true }
      }
    );

    chai.expect(response.status).to.equal(400);
    chai.expect(response.data.structured).to.equal('sql/failed');
        

  });
});
```

À noter, dans chaque test, on utilise la librairie `axios` afin de créer et envoyer des requêtes HTTP. Il faut installer le package :

```sh
npm install --save-dev axios
```

Enfin, pour simplifier cette démo, nous avons désactivé la sécurité sur la route `/user`. Dans la réalité, votre test devrait d'abord se connecter avec un utilisateur que vous avez défini dans vos données de départ, puis effectuer le test e2e. Dans `src/controllers/UserController.ts` :

```ts
// @Security('jwt')  
```

### Lancer nos tests e2e

Avant de lancer le test e2e, nous allons ajouter 2 scripts à notre `package.json` :

```json
 "scripts": {
    ...
    "e2e": "env-cmd -f ./test/.env.test npm run e2e-no-env",
    "e2e-no-env": "mocha -r ts-node/register \"test/e2e/suites/**/*.test.ts\""

```

Comme avant, on ajoute une qui utilise `env-cmd` pour charger les variables d'environnement et un autre sans.

Vous pouvez maintenant lancer les tests e2e:

```sh
npm run e2e
```

Avec le résultat :

```
  User CRUD
info: API Listening on port 21011 {"tag":"exec"}
2022-09-29T16:22:36.355Z 200 POST /auth/user 8 10.573
    ✔ Create a new user (68ms)
error: Duplicate entry 'kevin@nguni.fr' for key 'email' {"details":{"code":400,"details":{"sqlCode":"ER_DUP_ENTRY","sqlState":"23000"},"message":"Duplicate entry 'kevin@nguni.fr' for key 'email'","path":"/auth/user","structured":"sql/failed"},"tag":"exec"}
2022-09-29T16:22:36.366Z 400 POST /auth/user 175 3.291
    ✔ Create the same user twice returns an error


  2 passing (420ms)
```

Notez comment on a testé une réponse négative, et qu'un code 400 est retourné.

## La suite

Nos tests sont bien en place !

On est prêt à avancer dans la configuration de **Continuous Integration**.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.glassworks.tech/devops/ci/030-tests-e2e.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
