Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abgabe SWQ #51

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI Pipeline

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

services:
mongodb:
image: mongo:4.4
ports:
- 27017:27017

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Set up Node.js
uses: actions/setup-node@v3

- name: Install backend dependencies
run: |
cd backend
npm install

- name: Install frontend dependencies
run: |
cd frontend
npm install

- name: Run backend tests
run: |
cd backend
npm test

- name: Start backend server
run: |
cd backend
npm start &
env:
MONGO_URL: mongodb://localhost:27017/testdb # Falls benötigt

- name: Run Cypress tests
run: |
cd frontend
npx cypress run

- name: Run SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

# Optional: Quality Gate Check (kommentieren Sie die folgenden Zeilen ein, falls gewünscht)
# - name: SonarQube Quality Gate Check
# uses: sonarsource/sonarqube-quality-gate-action@master
# timeout-minutes: 5
# env:
# SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
139 changes: 139 additions & 0 deletions BERICHT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Projektbericht: CI/CD - Softwarequalität WS24

## Teammitglieder
- Marvin Eisenberg, Matrikelnummer: 30243419

## 1. Vorgehensweise
### 1.1. Vorbereitung

- **Fork des Repositories erstellen**:<br>
Bevor ich mit der Bearbeitung des Codes und dem Erstellen von Testfällen starten konnte, war es notwendig das Original Github Repo zu forken. Dies geht direkt auf der Github Repo Seite über den Button "Fork". Anschließend habe ich eine geforkte Kopie des Original Repos in meinem Github Account "newt0nDE".

- **Github Codespaces einrichten**:<br>
Der erste für mich neue Schritte beim Bearbeiten der Aufgabe war es, Github Codespaces anstatt meiner lokalen Jetbrains Webstorm Umgebung zu nutzen. Das Erstellen eines neuen Code-Spaces ging direkt aus dem Repository auf der Github Webseite und war erstaunlich einfach. Die Umstellung auf VS Code war zum Glück ebenfalls wenig aufregend.

- **Todo Anwendung starten & verstehen**:<br>
Um die Aufgabe sinnvoll lösen zu können war es natürlich wichtig die Anwendung auch selber mal zu starten und sich ein wenig mit der aktuellen Code Basis auseinandern zu setzen. Die Anwendung selber startet man mit "npm start", wichtig hierbei ist es vorher in das /backend Verzeichnis zu wechseln. Bevor man "npm start" ausführt muss man einmalig auch "npm install" ausführen, damit NodeJS die benötigten Abhängigkeiten installiert. Damit ein Zugriff auf die laufende Anwendung (in Codespace) erfolgreich ist musste ich hier die Port-Sicherheit im Codespace auf "öffentlich" setzen. Dann kann man über eine umgeleitete Webadresse auf die Anwendung zugreifen. **ACHTUNG**: Hinter die URL muss noch /todo.html hinzugefügt werden damit das klappt.

Sieht im Browser dann so aus: ![first_start](/images/first_start.png)

### 1.2. Erstellung automatisierter Tests

- **Backend Tests erstellen**:<br>
Zuerst habe ich die Test für das Backend erstellt. Dafür war bereits eine Datei /backend/todo.test.js im Repo vorhanden, in der Testfälle für das Backend vorkonfiguriert waren. Als Testtool wurde hier das NodeJS Modul "Supertest" genutzt. Hier musste ich also lediglich die vorhandenen Testfälle ergänzen/ anpassen. Wichtig war es hierbei alle CRUD-Operationen abzudecken. Sprich:
1. Create: Neue Todos erstellen
2. Read: Todos ausgeben
3. Update: Bestehendes Todo aktualisieren
4. Delete: Bestehendes Todo löschen

- **Backend Tests ausführen**:<br>
Die erstellen Testfälle für das Backend lassen sich mit dem Befehl "npm test" ausführen, anschließend erhält man eine Zusammenfassung über die Ergebnisse: ![backend_tests](/images/backend_tests.png)

- **Frontend Tests erstellen**:<br>
Die Frontend Test wurden mit dem NodeJS Paket "Cypress" erstellt und durchgeführt. Dafür war es notwendig eine Verzeichnisstruktur anzulegen mit der Cypress arbeiten kann: /frontend/cypress/e2e. Bei der Durchführung der Tests mit Cypress erstellt Cypress dann selber noch das Verzeichnis /frontend/cypress/screenshots. Hier legt Cypress bei gefundenen Fehlern einen Screenshot vom integrierten Chromium ab, aus dem ersichtlich wird wo ein Test fehlschlägt und wie die Webanwendung zum Zeitpunkt des Fehlers aussieht. Im /frontend Verzeichnis musste noch eine Cypress-Konfiguration erstellt werden. Hierdrin wird Cypress mitgeteilt wo sich die Testfälle befinden und auf welchem Port die zu testende Anwendung ausgeführt wird. Übrigens habe ich auch hier wieder Testfälle für alle CRUD-Operationen erstellt, ähnlich zu den Backend Tests.

- **Frontend Tests ausführen**:<br>
Die Ausführung der Frontend Tests mit Cypress sollten wir lokal und nicht in Codespaces ausführen, wahrscheinlich weil der Befehl "npx cypress open" hier nicht funktioniert und die Weboberfläche die sich öffnet dann nicht korrekt gerendert wird: ![cypress_in_codespaces](/images/cypress_in_codespaces.png)

Ich habe mich auf Grund des Aufwands (Hin- und Herkopieren zwischen des Repos zwischen Lokal und Codespaces) dagegen entschieden es lokal zu machen und einfach stattdessen den Befehl "npx cypress run" zu nutzen. Hier wird Cypress dann einfach ohne GUI ausgeführt und die Browser-Sitzung simuliert. Fehler werden dann als Screenshot gerendert und, wie bereits oben erwähnt, im Verzeichnis /screenshots gespeichert.

Noch ein wichtiger Hinweis: Damit das klappt muss in einer 2. Konsolensitzung zuerst die NodeJS Anwendung gestartet werden und im Hintergrund laufen und erreichbar sein.

Wenn ein Test fehlschlägt sieht das ca. so aus: ![cypress_error](/images/cypress_error.png)


### 1.3. Einrichtung der GitHub-Action
Github Actions Workflows sind sequenzielle Workflows die in einer zentralen YAML-Datei konfiguriert werden können um definierte Tests und Qualitätssicherungen direkt vor einem Push/Pull auf einem Repository durzuführen. Zum erstellen des Workflows war es also notwendig diese YAML-Datei zu erstellen. Diese legt man im Repo an unter .github/workflows/main.yml. In dieser Datei werden auch Vorbedingungen angelegt die notwendig sind um die Tests durchzuführen, wie zum Beispiel die Konfiguration einer MongoDB Datenbank Instanz. Wichtige Keys sind hier also:
1. name: Name des Workflows
2. on: definiert wann der Workflow getriggert werden soll
3. jobs/services: Zusätzliche Dienste/ Abhängigkeiten wie MongoDB
4. jobs/steps: Einzelne Schritte zu Testausführung

Ob der Github Actions Workflow funktioniert kann man testen, indem man einen Pullrequest erstellt - vor dem Merge wird der Workflow getriggert und bei Fehlern ist ein Merge nicht möglich. Hier muss dann entweder der Workflow oder der eingereichte Code überarbeitet werden.

Sieht im Github dann so aus: ![github_actions](/images/github_actions.png)

Wichtige Hinweise noch: Wie auch schon beim lokalen ausführen der Test ist es wichtig vor dem Starten der Tests in die korrekten Verzeichnisse zu wechseln! Das lässt sich ebenfalls in der YAML hinterlegen.

### 1.4. Integration von SonarQube
- **Projektanlage in SonarQube**:
SonarQube ist ein Plattform-Tool zur statischen Codeanalyse.

"SonarQube analysiert den Quelltext hinsichtlich folgender Qualitätsbereiche:

1. Doppelter Code
2. Abdeckung durch Modultests
3. Komplexität
4. Potentielle Fehler
5. Kodierrichtlinien
6. Kommentare"

(zitiert von SonarQube's Wikipedia Seite, Zugriff am 18.11.2024 20:30 uhr)

Zur Nutzung von SonarQube haben wir die folgende URL erhalten:
[hopper.fh-swf.de/sonarqube](https://hopper.fh-swf.de/sonarqube)

Hier kann man sich mit seinem bestehenden Github Account anmelden und ein neues lokales Projekt erstellen. Dabei vergibt man einen Namen und einen Key, diese sind in meinem Fall identisch und lauten wie mein Repo "todo_maeis005". Außerdem muss man seinen Main-Branch auswählen, da dieser bei mir aber tatsächlich auch "Main" heißt, war keine Anpassung notwendig.

Der nächste Schritt war die Erstellung von Env-Variablen in meinen Repo-Settings um dem SonarQube Zugriff auf meinen Code zu geben. Dazu in Github unter /repo/settings/security "new repository key" klicken und 2 Key erstellen. Einen für die "SONAR_HOST_URL" (hopper.fh-swf.de) und einen für den "SONAR_TOKEN", dieser wird bei dem Erstellen des Projektes in SonarQube mitgeteilt.

Der letzte notwendige Schritt war dann das Anpassen der Github Actions YAML-Datei, damit die statische Codeanalyse Teil des Testvorgangs wird. Die Anassung umfasst dann das Hinzufügen dieser Werte:

```yaml
- name: Run SonarQube Scan
uses: sonarsource/sonarqube-scan-action@master
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
```

Wenn man jetzt den Github Actions Workflow anstartet kann man in der Weboberfläche des SonarQubes erste Ergebnisse für die Codeanalyse seines Repos bewundern:

![sonar_results](/images/sonar_results.png)

Besonderen Augenmerkt sollte man dabei dann wahrscheinlich auf den Reiter "Security" legen. Zum Glück ist hier nichts aufgefallen.

### 1.5. Pull-Request im Original Repo erstellen
Nachdem dann alles funktioniert hat war es an der Zeit meine Ergebnisse samt diesem Bericht als Pull-Request im Original Repo abzugeben und auf gute Note zu hoffen ;).

## 2. Aufgetretene Probleme und deren Lösungen
Während der Bearbeitung der Aufgaben und dem anschließenden Test musste ich auch ein paar Fehler im Code korrigieren. Der schwerwiegendste Fehler war die Anpassung der Funktion "createTodoElement()" in todo.js. Hier mussten die Anführungszeichen angepasst werden damit eine korrekte Zwischenspeicherung und Übergabe an das Backend gewährleistet werden kann. Hier die korrekte Version des Codes:

```javascript
function createTodoElement(todo) {
let list = document.getElementById("todo-list");
let due = new Date(todo.due);
list.insertAdjacentHTML("beforeend",
`<div class="todo" id="todo-${todo._id}">
<div class="title">${todo.title}</div>
<div class="due">${due.toLocaleDateString(('de-DE'))}</div>
<div class="actions">
<button class="status" onclick="changeStatus('${todo._id}')">${status[todo.status || 0]}</button>
<button class="edit" onclick="editTodo('${todo._id}')">Bearbeiten</button>
<button class="delete" onclick="deleteTodo('${todo._id}')">Löschen</button>
</div>
</div>`);
}
```

Außerdem wurden diverse Korrekturen am Backend-Code vorgenommen. Darunter auch die Abschaltung der Keycloak Authentifizierung, da dieser Dienst zum Zeitpunkt der Erstellung dieses Berichts Probleme verursacht hatte.

```javascript
try {
const response = await axios.post(tokenEndpoint,
{
'grant_type': 'password',
'client_id': keycloakConfig.clientId,
'username': 'public',
'password': 'todo',
},
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
});

return response.data.access_token;
} catch (error) {
console.error('Fehler beim Abrufen des Keycloak-Tokens', error);
return null;
}
```
37 changes: 21 additions & 16 deletions backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ app.put('/todos/:id', authenticate,
async (req, res) => {
let id = req.params.id;
let todo = req.body;
todo._id = id;
if (todo._id !== id) {
console.log("id in body does not match id in path: %s != %s", todo._id, id);
res.sendStatus(400, "{ message: id in body does not match id in path}");
Expand Down Expand Up @@ -269,23 +270,27 @@ app.put('/todos/:id', authenticate,
* '500':
* description: Serverfehler
*/
app.post('/todos', authenticate,
async (req, res) => {
let todo = req.body;
if (!todo) {
res.sendStatus(400, { message: "Todo fehlt" });
return;
}
return db.insert(todo)
.then(todo => {
res.status(201).send(todo);
})
.catch(err => {
console.log(err);
res.sendStatus(500);
});
app.post('/todos', authenticate, async (req, res) => {
const { title, due, status, ...extraFields } = req.body;

// Validierung der erforderlichen Felder
if (!title || !due || typeof status !== 'number') {
return res.status(400).json({ error: "Bad Request", message: "Ungültige oder fehlende Felder im Todo" });
}
);

// Überprüfung auf unerwartete Felder
if (Object.keys(extraFields).length > 0) {
return res.status(400).json({ error: "Bad Request", message: "Unerwartete Felder im Todo-Objekt" });
}

try {
const todo = await db.insert({ title, due, status });
return res.status(201).send(todo);
} catch (err) {
console.error(err);
return res.status(500).json({ error: "Internal Server Error", message: "Fehler beim Erstellen des Todos" });
}
});

/** Delete a todo by id.
* @swagger
Expand Down
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@
"swagger-jsdoc": "^6.2.8",
"swagger-ui-express": "^4.6.2"
}
}
}
16 changes: 6 additions & 10 deletions backend/todo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ import getKeycloakToken from './utils';
let token; // Speichert den abgerufenen JWT-Token

beforeAll(async () => {
token = await getKeycloakToken();
});

describe('GET /todos (unautorisiert)', () => {
it('sollte einen 401-Fehler zurückgeben, wenn kein Token bereitgestellt wird', async () => {
const response = await request(app).get('/todos'); // Kein Authorization-Header

expect(response.statusCode).toBe(401);
expect(response.body.error).toBe('Unauthorized');
});
try {
token = await getKeycloakToken();
} catch (error) {
console.error('Fehler beim Abrufen des Keycloak-Tokens:', error);
token = null; // Setzt einen Standardwert, um den Testablauf nicht zu unterbrechen
}
});

describe('GET /todos', () => {
Expand Down
9 changes: 9 additions & 0 deletions frontend/cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const { defineConfig } = require('cypress');

module.exports = defineConfig({
e2e: {
baseUrl: 'http://localhost:3000', // Setzen Sie dies auf die URL Ihrer Anwendung
supportFile: false, // Falls Sie keine spezielle support/index.js Datei verwenden möchten
specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}' // Passen Sie dies an Ihre Struktur an
},
});
29 changes: 29 additions & 0 deletions frontend/cypress/e2e/change_status.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
describe('Todo App - Status Ändern', () => {
beforeEach(() => {
// Besuchen Sie die Anwendung vor jedem Test
cy.visit('/todo.html'); // Ersetzen Sie diesen Wert mit der tatsächlichen URL
});

it('soll den Status eines Todos ändern und dann löschen', () => {
// Erstellen eines neuen Todos
cy.get('#todo').type('Neues Todo zum Status ändern');
cy.get('#due').type('2024-12-31'); // Geben Sie ein geeignetes Datum ein
cy.get('#status').select('offen');

// Klicken Sie auf den "Hinzufügen"-Button
cy.get('form#todo-form input[type="submit"]').click();

// Überprüfen, ob das neue Todo in der Liste erscheint
cy.get('#todo-list .todo').last().should('contain', 'Neues Todo zum Status ändern');

// Das neu erstellte Todo finden und den Status ändern
cy.get('#todo-list .todo').last().within(() => {
cy.get('.status').click(); // Klicken Sie auf den Status-Button
});

// Überprüfen, ob sich der Status geändert hat
cy.get('#todo-list .todo').last().within(() => {
cy.get('.status').should('not.contain', 'offen'); // Annahme: Der Status hat sich geändert
});
});
});
20 changes: 20 additions & 0 deletions frontend/cypress/e2e/create_todo.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
describe('Todo App - Todo erstellen', () => {
beforeEach(() => {
// Besuchen Sie die Anwendung vor jedem Test
cy.visit('/todo.html'); // Ersetzen Sie diesen Wert mit der tatsächlichen URL
});

it('soll ein neues Todo hinzufügen und dann löschen', () => {
// Füllt das Formular aus
cy.get('#todo').type('Neues Todo hinzufügen');
cy.get('#due').type('2024-12-31'); // Geben Sie ein geeignetes Datum ein
cy.get('#status').select('offen');

// Klicken Sie auf den "Hinzufügen"-Button
cy.get('form#todo-form input[type="submit"]').click();

// Überprüfen, ob das neue Todo in der Liste erscheint
cy.get('#todo-list .todo').last().should('contain', 'Neues Todo hinzufügen');
cy.get('#todo-list .todo').last().should('contain', '31.12.2024');
});
});
28 changes: 28 additions & 0 deletions frontend/cypress/e2e/delete_todo.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
describe('Todo App - Todo löschen', () => {
beforeEach(() => {
// Besuchen Sie die Anwendung vor jedem Test
cy.visit('/todo.html'); // Ersetzen Sie diesen Wert mit der tatsächlichen URL
});

it('soll ein neues Todo hinzufügen und dann löschen', () => {
// Erstellen eines neuen Todos
cy.get('#todo').type('Neues Todo zum Löschen');
cy.get('#due').type('2024-12-31'); // Geben Sie ein geeignetes Datum ein
cy.get('#status').select('offen');

// Klicken Sie auf den "Hinzufügen"-Button
cy.get('form#todo-form input[type="submit"]').click();

// Überprüfen, ob das neue Todo in der Liste erscheint
cy.get('#todo-list .todo').last().should('contain', 'Neues Todo zum Löschen');
cy.get('#todo-list .todo').last().should('contain', '31.12.2024');

// Das neu erstellte Todo löschen
cy.get('#todo-list .todo').last().within(() => {
cy.get('.delete').click(); // Klicken Sie auf den Löschen-Button
});

// Überprüfen, ob das Todo aus der Liste entfernt wurde
cy.get('#todo-list .todo').should('not.contain', 'Neues Todo zum Löschen');
});
});
Loading