One place for hosting & domains

      So testen Sie ein Node.js-Modul mit Mocha und Assert


      Die Autorin wählte den Open Internet/Free Speech Fund, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      Testen ist ein integraler Bestandteil der Softwareentwicklung. Es ist üblich, dass Programmierer Code ausführen, der ihre Anwendung testet, während sie darin Änderungen vornehmen. So können sie bestätigen, dass sich die Anwendung so verhält, wie sie es gerne hätten. Mit dem richtigen Test-Setup kann dieser Prozess sogar automatisiert sein und somit eine Menge Zeit sparen. Das Ausführen von Tests nach dem Schreiben von neuem Code stellt sicher, dass neue Änderungen keine bereits vorhandenen Funktionen brechen. Das gibt dem Entwickler Vertrauen in seine Code-Basis, insbesondere dann, wenn der Code produktiv eingesetzt wird, damit die Benutzer mit ihm interagieren können.

      Ein Test-Framework strukturiert die Art, wie wir Testfälle erstellen. Mocha ist ein beliebtes JavaScript-Framework, das unsere Testfälle organisiert und für uns ausführt. Mocha verifiziert jedoch nicht das Verhalten unseres Codes. Um Werte in einem Test zu vergleichen, können wir das Node.js-assert-Modul verwenden.

      In diesem Artikel schreiben Sie Tests für ein Node.js-TODO-Listenmodul. Sie richten das Testframework von Mocha ein und nutzen es, um Ihre Tests zu strukturieren. Dann verwenden Sie das Node.js-assert-Modul, um die Tests selbst zu erstellen. In diesem Sinne verwenden Sie Mocha als Planersteller und assert zur Umsetzung des Plans.

      Voraussetzungen

      Schritt 1 — Schreiben eines Node-Moduls

      Beginnen wir diesen Artikel mit dem Schreiben des Node.js-Moduls, das wir testen möchten. Dieses Modul verwaltet eine Liste von TODO-Elementen. Mithilfe dieses Moduls können wir alle TODOs auflisten, die wir verfolgen, sowie neue Elemente hinzufügen und einige als abgeschlossen markieren. Zusätzlich können wir eine Liste von TODO-Elementen in eine CSV-Datei exportieren. Wenn Sie eine Auffrischung über das Schreiben von Node.js-Modulen wünschen, können Sie unseren Artikel Erstellen eines Node.js-Moduls lesen.

      Zuerst müssen wir die Codierungsumgebung einrichten. Erstellen Sie einen Ordner mit dem Namen Ihres Projekts in Ihrem Terminal. Dieses Tutorial verwendet den Namen todos:

      Gehen Sie dann in diesen Ordner hinein:

      Initialisieren Sie nun npm, da wir später zum Ausführen der Tests seine CLI-Funktionalität verwenden:

      Wir haben nur eine Abhängigkeit, Mocha, das wir zur Organisation und Durchführung unserer Tests verwenden. Zum Herunterladen und Installieren von Mocha verwenden Sie Folgendes:

      • npm i request --save-dev mocha

      Wir installieren Mocha als dev-Abhängigkeit, da es für das Modul in einer Produktionsumgebung nicht erforderlich ist. Wenn Sie mehr über Node.js-Packages oder npm erfahren möchten, lesen Sie den Leitfaden Verwenden von Node.js-Modulen mit npm und package.json.

      Abschließend erstellen wir unsere Datei, die den Code unseres Moduls enthalten wird:

      Damit können wir nun unser Modul erstellen. Öffnen Sie index.js in einem Texteditor wie nano:

      Zuerst definieren wir die Todos-Klasse. Diese Klasse enthält alle Funktionen, die wir zur Verwaltung unserer TODO-Liste benötigen. Fügen Sie index.js die folgenden Zeilen von Code hinzu:

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      }
      
      module.exports = Todos;
      

      Wir beginnen mit der Datei, indem wir eine Todos-Klasse erstellen. Seine Funktion constructor() nimmt keine Argumente an, daher müssen wir keine Werte bereitstellen, um ein Objekt für diese Klasse zu instanziieren. Wenn wir ein Todos-Objekt initialisieren, erstellen wir lediglich eine todos-Funktion, bei der es sich um ein leeres Array handelt.

      Die modules-Zeile ermöglicht es anderen Node.js-Modulen, unsere Todos-Klasse zu verlangen. Wenn wir sie nicht ausdrücklich exportieren, könnte die Testdatei, die wir später erstellen, sie nicht verwenden.

      Wir fügen nun eine Funktion hinzu, um das Array von todos, das wir gespeichert haben, auszugeben. Fügen Sie die folgenden hervorgehobenen Zeilen ein:

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      }
      
      module.exports = Todos;
      

      Unsere list()-Funktion gibt eine Kopie des Arrays aus, die von der Klasse verwendet wird. Sie erstellt die Kopie des Arrays mit der destrukturierten Syntax von JavaScript. Wir erstellen eine Kopie des Arrays, damit die Änderungen, die der Benutzer an dem von list() ausgegebenen Array vornimmt, nicht das vom Todos-Objekt verwendete Array beeinträchtigen.

      Anmerkung: JavaScript-Arrays sind Referenztypen. Das bedeutet, dass sich JavaScript bei jeder Variablenzuweisung an ein Array oder bei Funktionsaufrufen mit einem Array als Parameter auf das ursprüngliche Array bezieht, das erstellt wurde. Wenn wir zum Beispiel ein Array mit drei Elementen namens x haben und eine neue Variable y erstellen, sodass y = x, y und x sich beide auf dieselbe Sache beziehen. Alle Änderungen, die wir im Array an y vornehmen, wirken sich auf die Variable x aus und umgekehrt.

      Schreiben wir nun die add()-Funktion, die ein neues TODO-Element hinzufügt:

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      
          add(title) {
              let todo = {
                  title: title,
                  completed: false,
              }
      
              this.todos.push(todo);
          }
      }
      
      module.exports = Todos;
      

      Unsere add()-Funktion nimmt eine Zeichenfolge und platziert sie bei einem neuen JavaScript-Object in die title-Funktion. Das neue Objekt hat auch eine completed-Funktion, die standardmäßig auf false gesetzt ist. Dann fügen wir unser neues Objekt unserem Array von TODOs hinzu.

      Eine wichtige Funktionalität in einem TODO-Manager ist die Markierung von Elementen als abgeschlossen. Um dies umzusetzen, durchlaufen wir unser todos-Array, um das TODO-Element zu finden, nach dem der Benutzer sucht. Wenn eines gefunden wird, markieren wir es als abgeschlossen. Wenn keines gefunden wird, geben wir einen Fehler aus.

      Fügen Sie die complete()-Funktion wie hier gezeigt hinzu:

      todos/index.js

      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      
          add(title) {
              let todo = {
                  title: title,
                  completed: false,
              }
      
              this.todos.push(todo);
          }
      
          complete(title) {
              let todoFound = false;
              this.todos.forEach((todo) => {
                  if (todo.title === title) {
                      todo.completed = true;
                      todoFound = true;
                      return;
                  }
              });
      
              if (!todoFound) {
                  throw new Error(`No TODO was found with the title: "${title}"`);
              }
          }
      }
      
      module.exports = Todos;
      

      Speichern Sie die Datei und beenden Sie den Texteditor.

      Wir verfügen nun über einen grundlegenden TODO-Manager, mit dem wir experimentieren können. Als Nächstes testen wir den Code manuell, um zu sehen, ob die Anwendung funktioniert.

      Schritt 2 — Manuelles Testen des Codes

      In diesem Schritt führen wir die Funktionen unseres Codes aus und beobachten die Ausgabe, um sicherzustellen, dass sie unseren Erwartungen entspricht. Das nennt sich manuelles Testen. Es ist wahrscheinlich die gebräuchlichste Testmethodik, die Programmierer anwenden. Obwohl wir unsere Tests später mit Mocha automatisieren, testen wir unseren Code zunächst manuell, um ein besseres Gefühl dafür zu bekommen, wie sich manuelles Testen von Test-Frameworks unterscheidet.

      Wir fügen unserer Anwendung zwei TODO-Elemente hinzu und markieren eines als abgeschlossen. Starten Sie die Node.js REPL in dem gleichen Ordner wie die Datei index.js:

      Sie sehen die Eingabeaufforderung > in der REPL, die uns anzeigt, dass wir JavaScript-Code eingeben können. Geben Sie Folgendes an der Eingabeaufforderung ein:

      • const Todos = require('./index');

      Mit require() laden wir das TODOs-Modul in eine Todos-Variable. Erinnern Sie sich, dass unser Modul die Todos-Klasse standardmäßig ausgibt.

      Lassen Sie uns nun ein Objekt für diese Klasse instanziieren. Fügen Sie diese Zeile von Code in der REPL hinzu:

      • const todos = new Todos();

      Wir können das todos-Objekt verwenden, um unsere Umsetzungsarbeiten zu verifizieren. Fügen wir unser erstes TODO-Element hinzu:

      Bisher haben wir noch keine Ausgabe in unserem Terminal gesehen. Verifizieren wir, dass wir unser "run code"-TODO-Element gespeichert haben, indem wir eine Liste aller unserer TODOs abrufen:

      Sie sehen diese Ausgabe in Ihrer REPL:

      Output

      [ { title: 'run code', completed: false } ]

      Das ist das erwartete Ergebnis: Wir haben ein TODO-Element in unserem Array von TODOs und es ist nicht standardmäßig abgeschlossen.

      Wir fügen ein weiteres TODO-Element hinzu:

      • todos.add("test everything");

      Markieren Sie das erste TODO-Element als abgeschlossen:

      • todos.complete("run code");

      Unser todos-Objekt verwaltet nun zwei Elemente: "run code" und "test everything". Das "run code"-TODO wird ebenfalls abgeschlossen sein. Bestätigen wir dies, indem wir list() erneut aufrufen:

      Die REPL wird Folgendes ausgeben:

      Output

      [ { title: 'run code', completed: true }, { title: 'test everything', completed: false } ]

      Beenden Sie nun die REPL wie folgt:

      Wir haben bestätigt, dass sich unser Modul erwartungsgemäß verhält. Wir haben unseren Code nicht in eine Testdatei gestellt oder eine Testbibliothek verwendet, sondern manuell getestet. Leider ist diese Testform zeitaufwendig, wenn wir sie bei jeder Änderung, die wir vornehmen, durchführen. Als Nächstes verwenden wir automatisiertes Testen in Node.js und sehen, ob wir dieses Problem mit dem Mocha lösen können.

      Schritt 3 — Schreiben des ersten Tests mit Mocha und Assert

      Im letzten Schritt haben wir unsere Anwendung manuell getestet. Das funktioniert für individuelle Anwendungsfälle, aber wenn unser Modul skaliert, wird diese Methode weniger praktikabel. Wenn wir neue Eigenschaften testen, müssen wir sicher sein, dass die hinzugefügte Funktionalität keine Probleme in der alten Funktionalität verursacht. Wir möchten jede Eigenschaft bei jeder Änderung des Codes erneut testen, aber dies von Hand zu tun wäre sehr aufwendig und fehleranfällig.

      Eine effizientere Praxis wäre die Einrichtung automatisierter Tests. Das sind skriptbasierte Tests, die wie jeder andere Codeblock geschrieben sind. Wir führen unsere Funktionen mit definierten Eingaben aus und inspizieren ihre Effekte, um sicherzustellen, dass sie sich wie erwartet verhalten. Mit dem Anwachsen unserer Codebasis wächst auch der Umfang der automatisierten Tests. Wenn wir neue Tests zusammen mit den Eigenschaften schreiben, können wir überprüfen, ob das gesamte Modul noch funktioniert – ohne sich jedesmal daran erinnern zu müssen, wie jede einzelne Funktion genutzt wird.

      In diesem Tutorial verwenden wir das Testframework von Mocha mit dem Node.js-assert-Modul. Wir sammeln ein paar praktische Erfahrungen, um zu sehen, wie sie zusammenarbeiten.

      Erstellen Sie zunächst eine neue Datei, um unseren Testcode zu speichern:

      Verwenden Sie nun Ihren bevorzugten Texteditor, um die Testdatei zu öffnen. Sie können wie zuvor nano verwenden:

      In der ersten Zeile der Textdatei laden wir das TODOs-Modul, so wie wir es mit der Node.js-Shell getan haben. Dann laden wir das assert-Modul für das Schreiben unserer Tests. Fügen Sie die folgenden Zeilen hinzu:

      todos/index.test.js

      const Todos = require('./index');
      const assert = require('assert').strict;
      

      Die strict-Funktion des assert-Moduls erlaubt uns, spezielle Gleichheitstests anzuwenden, die von Node.js empfohlen werden und gut für das zukünftige Prüfen geeignet sind, da sie mehr Anwendungsfälle berücksichtigen.

      Bevor wir mit dem Schreiben von Tests beginnen, behandeln wir, wie Mocha unseren Code organisiert. Die in Mocha strukturierten Tests folgen normalerweise dieser Vorlage:

      describe([String with Test Group Name], function() {
          it([String with Test Name], function() {
              [Test Code]
          });
      });
      

      Beachten Sie zwei Schlüsselfunktionen: describe() und it(). Die describe()-Funktion wird zur Gruppierung ähnlicher Tests genutzt. Es ist nicht erforderlich, für Mocha Tests auszuführen, aber die Gruppierung von Tests erleichtert die Pflege unseres Testcodes. Es wird empfohlen, Ihre Tests so zu gruppieren, dass Sie ähnliche Tests leicht zusammen aktualisieren können.

      Die Funktion it() enthält unseren Testcode. Hier würden wir mit den Funktionen unseres Moduls interagieren und die assert-Bibliothek verwenden. Viele it()-Funktionen können als describe()-Funktion definiert werden.

      Unser Ziel in diesem Abschnitt ist die Verwendung von Mocha und assert zur Automatisierung unserer manuellen Tests. Wir führen dies Schritt für Schritt aus und beginnen mit unserem Beschreibungsblock. Fügen Sie Folgendes nach den Modulzeilen in Ihre Datei ein:

      todos/index.test.js

      ...
      describe("integration test", function() {
      });
      

      Mit diesem Codeblock haben wir eine Gruppierung für unsere integrierten Tests erstellt. Komponententests würden jeweils nur eine Funktion testen. Integrationstests verifizieren, wie gut Funktionen innerhalb oder über Module zusammenarbeiten. Wenn Mocha unseren Test ausführt, laufen alle Tests innerhalb des Beschreibungsblocks unter der "integration test"-Gruppe.

      Wir fügen nun eine it()-Funktion hinzu, damit wir mit dem Testen des Codes unseres Moduls beginnen können:

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
          });
      });
      

      Beachten Sie, wie deskriptiv wir den Namen des Tests gemacht haben. Wenn jemand unseren Test ausführt, wird sofort klar, was passiert oder fehlgeschlagen ist. Eine gut getestete Anwendung ist typischerweise eine gut dokumentierte Anwendung und Tests können manchmal eine effektive Art der Dokumentation sein.

      Für unseren ersten Test erstellen wir ein neues Todos-Objekt und verifizieren, dass es keine Elemente enthält:

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              assert.notStrictEqual(todos.list().length, 1);
          });
      });
      

      Die erste neue Zeile des Codes instanziierte ein neues Todos-Objekt, wie wir es in der Node.js-REPL oder einem anderen Modul tun würden. In der zweiten neuen Zeile verwenden wir das assert-Modul.

      Aus dem assert-Modul verwenden wir die notStrictEqual()-Methode. Diese Funktion nimmt zwei Parameter: den Wert, den wir testen möchten (genannt actual-Wert) und den Wert, den wir erhalten möchten (genannt expected-Wert). Wenn beide Argumente gleich sind, gibt notStrictEqual() einen Fehler aus, damit der Test fehlschlägt.

      Speichern und beenden Sie index.test.js.

      Der Basisfall wird wahr sein, da die Länge 0 sein sollte, was nicht 1 ist. Bestätigen wir das, indem wir Mocha ausführen. Dazu müssen wir unsere package.json-Datei ändern. Öffnen Sie Ihre package.json-Datein mit Ihrem Texteditor:

      Ändern Sie diese nun in der scripts-Funktion so wie hier gezeigt:

      todos/package.json

      ...
      "scripts": {
          "test": "mocha index.test.js"
      },
      ...
      

      Wir haben nun das Verhalten des npm-CLI-Befehls test geändert. Wenn wir npm test ausführen, überprüft npm den gerade eingegebenen Befehl in package.json. Die Ausführung sucht nach der Mocha-Bibliothek in unserem node_modules-Ordner und führt den mocha-Befehl mit unserer Testdatei aus.

      Speichern und beenden Sie package.json.

      Nun sehen wir uns an, was passiert, wenn wir unseren Test ausführen. Geben Sie Folgendes in Ihr Terminal ein:

      Der Befehl erzeugt die folgende Ausgabe:

      Output

      > todos@1.0.0 test your_file_path/todos > mocha index.test.js integrated test ✓ should be able to add and complete TODOs 1 passing (16ms)

      Diese Ausgabe zeigt uns zunächst, welche Testgruppe sie nun ausführen wird. Für jeden einzelnen Test innerhalb einer Gruppe ist der Testfall einbezogen. Wir sehen unseren Testnamen wie in der Funktion it() beschrieben. Das Häkchen auf der linken Seite des Testfalls zeigt an, dass der Test bestanden ist.

      Am Ende erhalten wir eine Zusammenfassung aller Tests. In unserem Fall ist unser einzelner Test bestanden und wurde in 16 ms abgeschlossen (die Zeit variiert von Computer zu Computer).

      Unsere Testung hat erfolgreich begonnen. Der aktuelle Testfall kann jedoch falsch-positive Meldungen liefern. Ein falsch-positiver Testfall ist ein Testfall, der bestanden wird, wenn er fehlschlagen sollte.

      Wir überprüfen gerade, dass die Länge des Arrays nicht gleich 1 ist. Wir werden den Test nun so ändern, dass dieser Zustand auch dann zutrifft, wenn er es nicht sollte. Fügen Sie der index.test.js folgende Zeilen hinzu:

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              todos.add("get up from bed");
              todos.add("make up bed");
              assert.notStrictEqual(todos.list().length, 1);
          });
      });
      

      Speichern und schließen Sie die Datei.

      Wir haben zwei TODO-Elemente hinzugefügt. Wir führen nun den Test aus, um zu sehen, was passiert:

      Dadurch ergibt sich Folgendes:

      Output

      ... integrated test ✓ should be able to add and complete TODOs 1 passing (8ms)

      Wie ewartet besteht der Test, da die Länge größer als 1 ist. Jedoch wird der ursprüngliche Zweck des ersten Tests verfehlt. Der erste Test ist dazu gedacht, zu bestätigen, dass wir mit einem Leerzustand beginnen. Ein besserer Test bestätigt dies in allen Fällen.

      Wir ändern nun den Test, damit er nur dann bestanden wird, wenn wir absolut keine TODOs im Speicher haben. Führen Sie die folgenden Änderungen in der index.test.js aus:

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              todos.add("get up from bed");
              todos.add("make up bed");
              assert.strictEqual(todos.list().length, 0);
          });
      });
      

      Sie haben notStrictEqual() auf strictEqual() geändert, eine Funktion, die die Gleichheit zwischen dem tatsächlichen und erwarteten Argument überprüft. Die Strict-Equal-Funktion schlägt fehl, wenn unsere Argumente nicht genau gleich sind.

      Speichern und beenden Sie und führen Sie dann den Test aus, damit wir sehen können, was passiert:

      Dieses Mal zeigt die Ausgabe einen Fehler:

      Output

      ... integration test 1) should be able to add and complete TODOs 0 passing (16ms) 1 failing 1) integration test should be able to add and complete TODOs: AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B: + expected - actual - 2 + 0 + expected - actual -2 +0 at Context.<anonymous> (index.test.js:9:10) npm ERR! Test failed. See above for more details.

      Dieser Text wird uns helfen, herauszufinden, warum der Test fehlgeschlagen ist. Beachten Sie, dass zu Beginn des Testfalls kein Häkchen vorhanden ist, da der Test fehlgeschlagen ist.

      Unsere Testzusammenfassung befindet sich nicht mehr am Ende der Ausgabe, sondern direkt nach der Anzeige unserer Liste von Testfällen:

      ...
      0 passing (29ms)
        1 failing
      ...
      

      Die verbleibende Ausgabe gibt uns Daten über unsere fehlgeschlagenen Tests. Zuerst sehen wir, welcher Testfall fehlgeschlagen ist:

      ...
      1) integrated test
             should be able to add and complete TODOs:
      ...
      

      Dann sehen wir, warum unser Test fehlgeschlagen ist:

      ...
            AssertionError [ERR_ASSERTION]: Input A expected to strictly equal input B:
      + expected - actual
      
      - 2
      + 0
            + expected - actual
      
            -2
            +0
      
            at Context.<anonymous> (index.test.js:9:10)
      ...
      

      Es wird ein AssertionError gemeldet, wenn strictEqual() fehlschlägt. Wir sehen, dass der expected-Wert, 0, vom actual-Wert, 2, abweicht.

      Dann sehen wir die Zeile in unserer Testdatei, in der der Code fehlschlägt. In diesem Fall ist es Zeile 10.

      Nun haben wir selbst gesehen, dass unser Test fehlschlägt, wenn wir fehlerhafte Werte erwarten. Wir ändern unseren Testfall wieder auf seinen richtigen Wert. Öffnen Sie die Datei:

      Dann nehmen Sie die todos.add-Zeilen heraus, sodass Ihr Code wie folgt aussieht:

      todos/index.test.js

      ...
      describe("integration test", function () {
          it("should be able to add and complete TODOs", function () {
              let todos = new Todos();
              assert.strictEqual(todos.list().length, 0);
          });
      });
      

      Speichern und schließen Sie die Datei.

      Führen Sie ihn erneut aus, um zu bestätigen, dass er ohne potenzielle falsch-positive Meldungen besteht:

      Sie erhalten folgende Ausgabe:

      Output

      ... integration test ✓ should be able to add and complete TODOs 1 passing (15ms)

      Wir haben nun die Belastbarkeit unseres Tests deutlich verbessert. Fahren wir mit unserem Integrationstest fort. Der nächste Schritt ist das Hinzufügen eines neuen TODO-Elements in index.test.js:

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              assert.strictEqual(todos.list().length, 0);
      
              todos.add("run code");
              assert.strictEqual(todos.list().length, 1);
              assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);
          });
      });
      

      Nach der Verwendung der add()-Funktion bestätigen wir, dass wir nun ein TODO haben, das von unserem todos-Objekt mit strictEqual() verwaltet wird. Unser nächster Test bestätigt die Daten in den todos mit deepStrictEqual(). Die Funktion deepStrictEqual() prüft rekursiv, ob unsere erwarteten und tatsächlichen Objekte die gleichen Eigenschaften haben. In diesem Fall testet sie, dass die von uns erwarteten Arrays beide ein JavaScript-Objekt beinhalten. Dann überprüft sie, dass ihre JavaScript-Objekte die gleichen Eigenschaften haben, d. h., ihre beiden title-Eigenschaften "run code" und ihre beiden completed-Eigenschaften false sind.

      Dann schließen wir die restlichen Tests unter Verwendung dieser beiden Gleichheitsprüfungen nach Bedarf durch Hinzufügen der folgenden hervorgehobenen Zeilen ab:

      todos/index.test.js

      ...
      describe("integration test", function() {
          it("should be able to add and complete TODOs", function() {
              let todos = new Todos();
              assert.strictEqual(todos.list().length, 0);
      
              todos.add("run code");
              assert.strictEqual(todos.list().length, 1);
              assert.deepStrictEqual(todos.list(), [{title: "run code", completed: false}]);
      
              todos.add("test everything");
              assert.strictEqual(todos.list().length, 2);
              assert.deepStrictEqual(todos.list(),
                  [
                      { title: "run code", completed: false },
                      { title: "test everything", completed: false }
                  ]
              );
      
              todos.complete("run code");
              assert.deepStrictEqual(todos.list(),
                  [
                      { title: "run code", completed: true },
                      { title: "test everything", completed: false }
                  ]
          );
        });
      });
      

      Speichern und schließen Sie die Datei.

      Unser Test imitiert nun unseren manuellen Test. Mit diesen programmatischen Tests müssen wir die Ausgabe nicht kontinuierlich überprüfen, um zu sehen, ob unsere Tests bei der Ausführung bestehen. Üblicherweise möchte man jeden Aspekt der Verwendung testen, um sicherzustellen, dass der Code ordnungsgemäß getestet wird.

      Wir führen unseren Test erneut mit npm test aus, um diese bekannte Ausgabe zu erhalten:

      Output

      ... integrated test ✓ should be able to add and complete TODOs 1 passing (9ms)

      Sie haben nun einen integrierten Test mit dem Mocha-Framework und der assert-Bibliothek eingerichtet.

      Gehen wir nun von einer Situation aus, in der wir unser Modul mit einigen anderen Entwicklern geteilt haben und diese uns jetzt Feedback geben. Viele unserer Benutzer würden sich wünschen, dass die Funktion complete() einen Fehler meldet, wenn bisher noch keine TODOs hinzugefügt wurden. Wir fügen diese Funktionalität in unserer Funktion complete() ein.

      Öffnen Sie index.js in Ihrem Texteditor:

      Fügen Sie der Funktion Folgendes hinzu:

      todos/index.js

      ...
      complete(title) {
          if (this.todos.length === 0) {
              throw new Error("You have no TODOs stored. Why don't you add one first?");
          }
      
          let todoFound = false
          this.todos.forEach((todo) => {
              if (todo.title === title) {
                  todo.completed = true;
                  todoFound = true;
                  return;
              }
          });
      
          if (!todoFound) {
              throw new Error(`No TODO was found with the title: "${title}"`);
          }
      }
      ...
      

      Speichern und schließen Sie die Datei.

      Nun fügen wir einen neuen Test für diese neue Eigenschaft hinzu. Wir wollen verifizieren, ob ein Todos-Objekt, das keine Elemente enthält, unseren speziellen Fehler ausgibt, wenn wir es mit complete aufrufen.

      Gehen Sie in die index.test.js zurück:

      Fügen Sie am Ende der Datei den folgenden Code hinzu:

      todos/index.test.js

      ...
      describe("complete()", function() {
          it("should fail if there are no TODOs", function() {
              let todos = new Todos();
              const expectedError = new Error("You have no TODOs stored. Why don't you add one first?");
      
              assert.throws(() => {
                  todos.complete("doesn't exist");
              }, expectedError);
          });
      });
      

      Wie zuvor verwenden wir describe() und it(). Wir beginnen unseren Test mit der Erstellung eines neuen todos-Objekts. Dann definieren wir den Fehler, dessen Meldung wir erwarten, wenn wir die Funktion complete() aufrufen.

      Als Nächstes verwenden wir die Funktion throws() des assert-Moduls. Diese Funktion wurde erstellt, damit wir die Fehler, die unser Code ausgibt, verifizieren können. Sein erstes Argument ist eine Funktion, die den Code enthält, der den Fehler ausgibt. Das zweite Argument ist der Fehler, dessen Meldung wir erwarten.

      Führen Sie in Ihrem Terminal erneut die Tests mit npm test aus und Sie sehen die folgende Ausgabe:

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs 2 passing (25ms)

      Diese Ausgabe zeigt den Nutzen, warum wir automatisiertes Testen mit Mocha und assert durchführen. Da unsere Tests schriftlich ausgearbeitet sind, verifizieren wir bei jeder Ausführung von npm test, dass alle unsere Tests bestehen. Wir mussten nicht manuell überprüfen, ob der andere Code noch funktioniert – wir wissen, dass es so ist, da der Test, den wir haben, bestand.

      Bisher haben unsere Tests die Ergebnisse von synchronem Code verifiziert. Wir behandeln nun, wie wir unsere neu gewonnenen Testgewohnheiten anpassen müssten, um mit asynchronem Code arbeiten zu können.

      Schritt 4 – Testen von asynchronem Code

      Eine der Eigenschaften, die wir in unserem TODO-Modul benötigen, ist eine CSV-Exportfunktion. Damit werden alle gespeicherten TODOs zusammen mit dem abgeschlossenen Status in einer Datei ausgegeben. Das erfordert die Verwendung des Moduls fs – eines integrierten Node.js-Moduls für die Arbeit mit dem Dateisystem.

      Das Schreiben in eine Datei ist eine asynchrone Operation. Es gibt viele Möglichkeiten, in eine Datei in Node.js zu schreiben. Wir können Callbacks, Promises oder die Schlüsselworte async/await verwenden. In diesem Abschnitt behandeln wir, wie wir Tests für diese verschiedenen Methoden schreiben.

      Callbacks

      Eine callback-Funktion ist eine Funktion, die als Argument in einer asynchronen Funktion verwendet wird. Sie wird aufgerufen, wenn die asynchrone Operation abgeschlossen ist.

      Wir fügen unserer Todos-Klasse eine Funktion namens saveToFile() hinzu. Diese Funktion erstellt eine Zeichenfolge, indem Sie alle unsere TODO-Elemente durchläuft, und schreibt diese Zeichenfolge in eine Datei.

      Öffnen Sie Ihre index.js-Datei:

      Fügen Sie den folgenden hervorgehobenen Code in die Datei ein:

      todos/index.js

      const fs = require('fs');
      
      class Todos {
          constructor() {
              this.todos = [];
          }
      
          list() {
              return [...this.todos];
          }
      
          add(title) {
              let todo = {
                  title: title,
                  completed: false,
              }
              this.todos.push(todo);
          }
      
          complete(title) {
              if (this.todos.length === 0) {
                  throw new Error("You have no TODOs stored. Why don't you add one first?");
              }
      
              let todoFound = false
              this.todos.forEach((todo) => {
                  if (todo.title === title) {
                      todo.completed = true;
                      todoFound = true;
                      return;
                  }
              });
      
              if (!todoFound) {
                  throw new Error(`No TODO was found with the title: "${title}"`);
              }
          }
      
          saveToFile(callback) {
              let fileContents = 'Title,Completedn';
              this.todos.forEach((todo) => {
                  fileContents += `${todo.title},${todo.completed}n`
              });
      
              fs.writeFile('todos.csv', fileContents, callback);
          }
      }
      
      module.exports = Todos;
      

      Zunächst müssen wir das fs-Modul in unsere Datei importieren. Dann haben wir unsere neue Funktion saveToFile() hinzugefügt. Unsere Funktion übernimmt eine Callback-Funktion, die genutzt wird, sobald die Schreiboperation der Datei abgeschlossen ist. In dieser Funktion erstellen wir eine fileContents-Variable, die die gesamte zu speichernde Zeichenfolge als Datei speichert. Sie wird mit den CSV-Titeln initialisiert. Dann durchlaufen wir jedes TODO-Element mit der forEach()-Methode des internen Arrays. Beim Durchlaufen fügen wir die title– und completed-Eigenschaften der einzelnen todos hinzu.

      Zum Schluss verwenden wir das fs-Modul zum Schreiben der Datei mit der writeFile()-Funktion. Unser erstes Argument ist der Dateiname: todos.csv. Das zweite ist der Inhalt der Datei, in diesem Fall unsere fileContents-Variable. Das letzte Argument ist unsere Callback-Funktion, die alle Schreibfehler der Datei behandelt.

      Speichern und schließen Sie die Datei.

      Wir schreiben nun einen Test für unsere Funktion saveToFile. Unser Test führt zwei Dinge aus: Er überprüft die Existenz der Datei und verifiziert, dass sie den richtigen Inhalt hat.

      Öffnen Sie die Datei index.test.js:

      Beginnen wir damit, das fs-Modul am Anfang der Datei zu laden, da wir es zum Testen unserer Ergebnisse verwenden werden:

      todos/index.test.js

      const Todos = require('./index');
      const assert = require('assert').strict;
      const fs = require('fs');
      ...
      

      Am Ende der Datei fügen wir unseren neuen Testfall hinzu:

      todos/index.test.js

      ...
      describe("saveToFile()", function() {
          it("should save a single TODO", function(done) {
              let todos = new Todos();
              todos.add("save a CSV");
              todos.saveToFile((err) => {
                  assert.strictEqual(fs.existsSync('todos.csv'), true);
                  let expectedFileContents = "Title,Completednsave a CSV,falsen";
                  let content = fs.readFileSync("todos.csv").toString();
                  assert.strictEqual(content, expectedFileContents);
                  done(err);
              });
          });
      });
      

      Wie zuvor verwenden wir describe(), um unseren Test getrennt von den anderen zu gruppieren, da er eine neue Funktionalität enthält. Die it()-Funktion unterscheidet sich leicht von unseren anderen. Normalerweise hat die von uns verwendete Callback-Funktion keine Argumente. Dieses Mal haben wir done als Argument. Wir benötigen dieses Argument, wenn wir Funktionen mit Callbacks testen. Die Callback-Funktion done() wird von Mocha verwendet, um ihr anzugeben, wenn eine asynchrone Funktion abgeschlossen ist.

      Alle in Mocha getesteten Callback-Funktionen müssen den Callback done() aufrufen. Wäre dies nicht der Fall, würde Mocha nie wissen, ob die Funktion abgeschlossen ist und würde festgefahren auf ein Signal warten.

      Wir erstellen nun unsere Todos-Instanz und fügen ihr ein einzelnes Element hinzu. Wir rufen die Funktion saveToFile() mit einem Callback auf, der einen Dateischreibfehler findet. Beachten Sie, wie unser Test für diese Funktion im Callback enthalten ist. Wenn unser Testcode außerhalb des Callbacks wäre, würde er fehlschlagen, solange der Code aufgerufen würde, bevor das Schreiben der Datei abgeschlossen wäre.

      In unserer Callback-Funktion überprüfen wir zunächst, dass unsere Datei existiert:

      todos/index.test.js

      ...
      assert.strictEqual(fs.existsSync('todos.csv'), true);
      ...
      

      Die Funktion fs.existsSync() gibt true aus, wenn der Dateipfad in ihrem Argument existiert, und andernfalls false.

      Anmerkung: Die Funktionen des fs-Moduls sind standardmäßig asynchron. Sie bildeten jedoch für Schlüsselfunktionen synchrone Gegenstücke. Dieser Test ist einfacher, wenn synchrone Funktionen verwendet werden, da wir den asynchronen Code nicht schachteln müssen, um sicherzustellen, dass er funktioniert. Im fs-Modul enden synchrone Funktionen normalerweise mit "Sync" am Ende ihrer Namen.

      Dann erstellen wir eine Variable, um unseren erwarteten Wert zu speichern:

      todos/index.test.js

      ...
      let expectedFileContents = "Title,Completednsave a CSV,falsen";
      ...
      

      Wir verwenden readFileSync() des fs-Moduls zum synchronen Lesen der Datei:

      todos/index.test.js

      ...
      let content = fs.readFileSync("todos.csv").toString();
      ...
      

      Wir geben readFileSync() den richtigen Pfad für die Datei: todos.csv. Da readFileSync() ein Buffer-Objekt ausgibt, das Binärdaten speichert, verwenden wir seine toString()-Methode, damit wir seinen Wert mit der Zeichenfolge vergleichen können, die wir voraussichtlich gespeichert haben.

      Wie zuvor verwenden wir das strictEqual des assert-Moduls, um einen Vergleich durchzuführen:

      todos/index.test.js

      ...
      assert.strictEqual(content, expectedFileContents);
      ...
      

      Wir beenden unseren Test durch das Aufrufen des done()-Callbacks, sodass Mocha weiß, dass der Test dieses Falls gestoppt wird:

      todos/index.test.js

      ...
      done(err);
      ...
      

      Wir geben das err-Objekt zu done(), sodass der Test mit Mocha fehlschlägt, falls ein Fehler vorhanden ist.

      Speichern und beenden Sie index.test.js.

      Wie zuvor führen wir diesen Test mit npm test durch. Ihre Konsole zeigt dann diese Ausgabe:

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO 3 passing (15ms)

      Sie haben nun Ihre erste asynchrone Funktion mit Mocha unter der Verwendung von Callbacks getestet. Zum Zeitpunkt des Schreibens dieses Tutorials sind Promises jedoch verbreiteter als Callbacks in neuem Node.js-Code, wie auch in unserem Artikel Schreiben von asynchronem Code in Node.js beschrieben. Als Nächstes lernen wir, wie wir auch diese mit Mocha testen können.

      Promises

      Ein Promise ist ein JavaScript-Objekt, das letztendlich einen Wert ausgibt. Wenn ein Promise erfolgreich ist, ist es gelöst. Wenn es auf einen Fehler trifft, wird es verworfen.

      Wir ändern die saveToFile()-Funktion, damit sie Promises anstelle von Callbacks verwendet. Öffnen Sie index.js:

      Zuerst müssen wir ändern, wie das fs-Modul geladen ist. Ändern Sie in Ihrer index.js-Datei die require()-Aussage am Anfang der Datei, sodass sie aussieht wie folgt:

      todos/index.js

      ...
      const fs = require('fs').promises;
      ...
      

      Wir haben nun das fs-Modul importiert, das Promises anstelle von Callbacks verwendet. Nun müssen wir einige Änderungen an saveToFile() vornehmen, damit es stattdessen mit Promises arbeitet.

      Führen Sie in Ihrem Texteditor die folgenden Änderungen an der Funktion saveToFile() aus, um die Callbacks zu entfernen:

      todos/index.js

      ...
      saveToFile() {
          let fileContents = 'Title,Completedn';
          this.todos.forEach((todo) => {
              fileContents += `${todo.title},${todo.completed}n`
          });
      
          return fs.writeFile('todos.csv', fileContents);
      }
      ...
      

      Der erste Unterschied besteht darin, dass unsere Funktion keine Argumente mehr akzeptiert. Mit Promises benötigen wir keine Callback-Funktion. Die zweite Änderung betrifft die Weise, wie die Datei geschrieben ist. Wir geben nun das Ergebnis des writeFile()-Promises aus.

      Speichern und schließen Sie index.js.

      Wir passen unseren Test so an, dass er mit Promises funktioniert. Öffnen Sie index.test.js:

      Ändern Sie den saveToFile()-Test auf Folgendes:

      todos/index.js

      ...
      describe("saveToFile()", function() {
          it("should save a single TODO", function() {
              let todos = new Todos();
              todos.add("save a CSV");
              return todos.saveToFile().then(() => {
                  assert.strictEqual(fs.existsSync('todos.csv'), true);
                  let expectedFileContents = "Title,Completednsave a CSV,falsen";
                  let content = fs.readFileSync("todos.csv").toString();
                  assert.strictEqual(content, expectedFileContents);
              });
          });
      });
      

      Als erste Änderung müssen wir den done()-Callback aus den Argumenten entfernen. Wenn Mocha das done()-Argument durchläuft, muss es aufgerufen werden oder es gibt einen Fehler wie folgt aus:

      1) saveToFile()
             should save a single TODO:
           Error: Timeout of 2000ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves. (/home/ubuntu/todos/index.test.js)
            at listOnTimeout (internal/timers.js:536:17)
            at processTimers (internal/timers.js:480:7)
      

      Schließen Sie beim Testen von Promises den done()-Callback nicht in it() ein.

      Um unser Promise zu testen, müssen wir unseren Assertionscode in die then()-Funktion einfügen. Beachten Sie, dass wir dieses Promise im Test ausgeben. Wir haben keine catch()-Funktion, um das Promise auszufangen, wenn es verworfen wird.

      Wir geben das Promise aus, sodass alle Fehler, die in der then()-Funktion ausgegeben werden, in der it()-Funktion heraustreten. Wenn die Fehler nicht heraustreten, wird der Testfall mit Mocha nicht fehlschlagen. Beim Testen von Promises müssen Sie return auf das getestete Promise verwenden. Andernfalls besteht das Risiko, ein falsch-positives Ergebnis zu erhalten.

      Wir lassen auch die catch()-Klausel aus, da Mocha erkennen kann, wenn ein Promise verworfen wird. Im Fall einer Verwerfung schlägt der Test automatisch fehl.

      Da unser Test nun fertig ist, speichern und beenden Sie die Datei und führen Sie anschließend Mocha mit npm test aus. Als Bestätigung erhalten wir ein erfolgreiches Ergebnis:

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO 3 passing (18ms)

      Wir haben unseren Code und Test zur Verwendung von Promises geändert und wissen nun sicher, dass es funktioniert. Die neuesten asynchronen Muster verwenden jedoch async/await-Schlüsselwörter, damit wir nicht mehrere then()-Funktionen erstellen müssen, um erfolgreiche Ergebnisse zu bearbeiten. Sehen wir als Nächstes, wie wir mit async/await testen können.

      async/await

      Die Schlüsselwörter async/await erleichtern die Arbeit mit Promises, da sie nicht so ausführlich sind. Sobald wir eine Funktion als asynchron mit dem Schlüsselwort async definieren, können wir alle zukünftigen Ergebnisse in dieser Funktion mit dem Schlüsselwort await erhalten. Auf diese Weise können wir Promises verwenden, ohne die Funktionen then() oder catch() verwenden zu müssen.

      Wir können unseren auf Promises basierenden saveToFile()-Test mit async/await vereinfachen. Führen Sie in Ihrem Texteditor diese kleineren Änderungen im saveToFile()-Test in der index.test.js aus:

      todos/index.test.js

      ...
      describe("saveToFile()", function() {
          it("should save a single TODO", async function() {
              let todos = new Todos();
              todos.add("save a CSV");
              await todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,falsen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      });
      

      Die erste Änderung besteht darin, dass die von der it()-Funktion verwendete Funktion jetzt das Schlüsselwort async zur Definierung verwendet. Dadurch können wir das Schlüsselwort await in ihrem Körper verwenden.

      Die zweite Änderung tritt auf, wenn wir saveToFile() aufrufen. Bevor es aufgerufen wird, wird das Schlüsselwort await verwendet. Node.js wird nun warten, bis diese Funktion gelöst ist, bevor es den Test fortsetzt.

      Da wir den Code aus der then()-Funktion in den it()-Funktionskörper verschoben haben, ist unser Funktionscode leichter zu lesen. Die Ausführung dieses Codes mit npm test erzeugt diese Ausgabe:

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO 3 passing (30ms)

      Wir können jetzt asynchrone Funktionen testen, indem wir ein beliebiges von drei asynchronen Paradigmen entsprechend verwenden.

      Wir haben mit dem Testen von synchronem und asynchronem Code mit Mocha schon einen breiten Bereich abgedeckt. Als Nächstes tauchen wir tiefer ein in einige andere Funktionalitäten, die Mocha bietet, um unsere Testerfahrung zu verbessern. Besonders interessant ist hierbei auch, wie Hooks die Testumgebungen verändern können.

      Schritt 5 – Verwenden von Hooks zur Verbesserung von Testfällen

      Hooks sind ein nützlicher Bestandteil von Mocha, der es uns ermöglicht, die Umgebung vor und nach einem Test zu konfigurieren. Wir fügen Hooks typischerweise in einen describe()-Funktionsblock, da diese eine für einige Testfälle spezifische Auf- und Abbaulogik enthalten.

      Mocha bietet vier Hooks, die wir in unseren Tests verwenden können:

      • before: Dieser Hook wird einmal ausgeführt, bevor der erste Test beginnt.
      • beforeEach: Dieser Hook wird vor jedem Testfall ausgeführt.
      • after: Dieser Hook wird einmal ausgeführt, nachdem der letzte Testfall abgeschlossen ist.
      • afterEach: Dieser Hook wird nach jedem Testfall ausgeführt.

      Hooks sind sehr nützlich, wenn wir eine Funktion oder Eigenschaft mehrmals testen, da sie uns erlauben, den Einrichtungscode des Tests (wie das Erstellen des todos-Objekts) vom Assertionscode des Tests zu trennen.

      Um den Wert von Hooks zu sehen, fügen wir dem Testblock saveToFile() weitere Tests hinzu.

      Obwohl wir bestätigt haben, dass wir unsere TODO-Elemente in eine Datei speichern können, haben wir nur ein Element gespeichert. Außerdem wurde das Element nicht als abgeschlossen markiert. Wir fügen weitere Tests hinzu, um sicherzustellen, dass die verschiedenen Aspekte unseres Moduls funktionieren.

      Wir fügen zunächst einen zweiten Test hinzu, um zu bestätigen, dass unsere Datei korrekt gespeichert wird, wenn wir ein abgeschlossenes TODO-Element haben. Öffnen Sie Ihre Datei index.test.js mit Ihrem Texteditor:

      Ändern Sie den letzten Test folgendermaßen:

      todos/index.test.js

      ...
      describe("saveToFile()", function () {
          it("should save a single TODO", async function () {
              let todos = new Todos();
              todos.add("save a CSV");
              await todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,falsen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      
          it("should save a single TODO that's completed", async function () {
              let todos = new Todos();
              todos.add("save a CSV");
              todos.complete("save a CSV");
              await todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,truen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      });
      

      Der Test ist ähnlich wie zuvor. Die wichtigsten Unterschiede sind, dass wir die complete()-Funktion vor saveToFile() aufrufen, und unsere expectedFileContents nun true anstatt false für den Wert der completed-Kolumne haben.

      Speichern und schließen Sie die Datei.

      Wir führen unseren neuen Test und alle anderen mit npm test aus:

      Dadurch ergibt sich Folgendes:

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO ✓ should save a single TODO that's completed 4 passing (26ms)

      Es funktioniert wie erwartet. Es gibt jedoch Raum für Verbesserungen. Sie müssen ein Todos-Objekt zu Beginn des Tests instanziieren. Beim Hinzufügen von mehr Testfällen wird dies schnell repetitiv und vergeudet Speicherplatz. Außerdem erstellt der Test bei jeder Ausführung eine Datei. Das kann von jemandem, der sich mit dem Modul nicht so gut auskennt, mit einer tatsächlichen Ausgabe verwechselt werden. Es wäre schön, wenn wir unsere Ausgabedateien nach dem Testen bereinigen würden.

      Führen wir nun diese Verbesserungen mit Test-Hooks aus. Wir verwenden den Hook beforeEach(), um unsere Testvorrichtung von TODO-Elementen einzurichten. Eine Testvorrichtung ist jeder konsistente Zustand, der in einem Test verwendet wird. In unserem Fall ist unsere Testvorrichtung ein neues todos-Objekt, dem bereits ein TODO-Element hinzugefügt wurde. Wir verwenden afterEach(), um die vom Test erstellte Datei zu entfernen.

      Führen Sie in index.test.js die folgenden Änderungen an Ihrem letzten Test für saveToFile() aus:

      todos/index.test.js

      ...
      describe("saveToFile()", function () {
          beforeEach(function () {
              this.todos = new Todos();
              this.todos.add("save a CSV");
          });
      
          afterEach(function () {
              if (fs.existsSync("todos.csv")) {
                  fs.unlinkSync("todos.csv");
              }
          });
      
          it("should save a single TODO without error", async function () {
              await this.todos.saveToFile();
      
              assert.strictEqual(fs.existsSync("todos.csv"), true);
              let expectedFileContents = "Title,Completednsave a CSV,falsen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      
          it("should save a single TODO that's completed", async function () {
              this.todos.complete("save a CSV");
              await this.todos.saveToFile();
      
              assert.strictEqual(fs.existsSync('todos.csv'), true);
              let expectedFileContents = "Title,Completednsave a CSV,truen";
              let content = fs.readFileSync("todos.csv").toString();
              assert.strictEqual(content, expectedFileContents);
          });
      });
      

      Entschlüsseln wir alle vorgenommenen Änderungen. Wir haben dem Testblock ein beforeEach() hinzugefügt:

      todos/index.test.js

      ...
      beforeEach(function () {
          this.todos = new Todos();
          this.todos.add("save a CSV");
      });
      ...
      

      Diese beiden Zeilen Code erstellen ein neues Todos-Objekt, das in jedem unserer Tests verfügbar ist. Mit Mocha verweist das this-Objekt in beforeEach() auf dasselbe this-Objekt in it(). this ist für jeden Codeblock im describe()-Block gleich. Weitere Informationen über this finden Sie in unserem Tutorial Verstehen Sie This, Bind, Call und Apply in JavaScript.

      Diese leistungsstarke gemeinsame Nutzung des Kontexts ist der Grund, warum wir schnell Testvorrichtungen erstellen können, die für unsere beiden Tests funktionieren.

      Dann bereinigen wir unsere CSV-Datei in der afterEach()-Funktion:

      todos/index.test.js

      ...
      afterEach(function () {
          if (fs.existsSync("todos.csv")) {
              fs.unlinkSync("todos.csv");
          }
      });
      ...
      

      Wenn unser Test fehlgeschlagen ist, hat der Test möglicherweise keine Datei erstellt. Aus diesem Grund überprüfen wir, ob die Datei vorhanden ist, bevor wir die Funktion unlinkSync() verwenden, um diese zu löschen.

      Die verbleibenden Änderungen wechseln die Referenz von todos, die zuvor in der Funktion it() erstellt wurden, zu this.todos, das im Mocha-Kontext verfügbar ist. Wir haben auch die Zeilen gelöscht, die zuvor todos in den einzelnen Testfällen instanziierten.

      Führen wir nun diese Datei aus, um zu bestätigen, dass unsere Tests noch funktionieren. Geben Sie npm test in Ihr Terminal ein, um Folgendes zu erhalten:

      Output

      ... integrated test ✓ should be able to add and complete TODOs complete() ✓ should fail if there are no TODOs saveToFile() ✓ should save a single TODO without error ✓ should save a single TODO that's completed 4 passing (20ms)

      Die Ergebnisse sind gleich, und als zusätzlichen Vorteil haben wir die Einrichtungszeit für neue Tests für die Funktion saveToFile() leicht reduziert sowie eine Lösung für die zurückbleibende CSV-Datei gefunden.

      Zusammenfassung

      In diesem Tutorial haben Sie ein Node.js-Modul geschrieben, um TODO-Elemente zu verwalten und den Code manuell mit der Node.js-REPL getestet. Dann haben Sie eine Testdatei erstellt und das Mocha-Framework zur Ausführung automatisierter Tests verwendet. Mit dem assert-Modul konnten Sie verifizieren, ob Ihr Code funktioniert. Sie haben mit Mocha auch synchrone und asynchrone Funktionen getestet. Schließlich haben Sie Hooks mit Mocha erstellt, die das Schreiben mehrerer verwandter Testfälle wesentlich lesbarer und wartungsfreundlicher machen.

      Mit diesen neuen Kenntnissen können Sie nun versuchen, Tests für neue Node.js-Module zu schreiben, die Sie gerade erstellen. Können Sie über die Ein- und Ausgaben Ihrer Funktion nachdenken und Ihren Test schreiben, bevor Sie Ihren Code erstellen?

      Wenn Sie weitere Informationen über das Mocha-Framework erhalten möchten, besuchen Sie unsere offizielle Mocha-Dokumentation. Wenn Sie gerne noch mehr über Node.js lernen möchten, können Sie zu der Serienseite Codieren in Node.js zurückkehren.



      Source link

      Erstellen einer Anwendung für ein inspirierendes Zitat unter Verwendung von AdonisJs und MySQL


      Der Autor wählte den Tech Education Fund, um eine Spende im Rahmen des Programms Write for DOnations zu erhalten.

      Einführung

      AdonisJs ist ein in einfachem JavaScript geschriebenes Node.js Web-Framework, das auf allen gängigen Betriebssystemen läuft. Es verwendet das beliebte Designmuster MVC (Model – View – Controller) und bietet ein stabiles Ökosystem für das Schreiben serverseitiger Webanwendungen. Das Framework bietet nahtlose Authentifizierung, SQL ORM (objekt-relationales Mapping), Migrationen und Datenbank-Seeding. AdonisJs weist eine ähnliche Architektur wie das PHP Webanwendungs-Framework Laravel auf, einschließlich der gleichen Ordnerstruktur und mehrere gemeinsam genutzter Einrichtungskonzepte.

      Standardmäßig verwendet AdonisJs die Edge Template Engine, die für eine intuitive Verwendung konzipiert ist. Genau wie Laravel wird AdonisJs mit einem ORM namens Lucid bereitgestellt, das als Schnittstelle für die Kommunikation zwischen den Modellen einer Anwendung und der Datenbank dient. Mit AdonisJs können Entwickler eine Full-Stack-Anwendung erstellen, bei der der Backend-Server für die Anwendung der Geschäftslogik, das Routing und das Rendering aller Seiten der Anwendung verantwortlich ist. Es ist auch möglich, eine Webdienst-API zu erstellen, um JSON-Antworten von einem Controller zurückzugeben; diese Webdienste können dann über Frontend-Frameworks wie Vue.js, React und Angular konsumiert werden.

      In diesem Tutorial erstellen Sie eine Anwendung mit AdonisJs unter Verwendung seiner CLI. Sie erstellen Routen, Controller, Modelle und Ansichten innerhalb Ihrer Anwendung und führen Formularvalidierungen durch. Das Beispiel in diesem Tutorial wird eine Anwendung für ein inspirierendes Zitat sein, bei der sich ein Benutzer registrieren und anmelden kann, um ein inspirierendes Zitat zu erstellen. Diese Demo-Anwendung bietet Ihnen die Möglichkeit zur Ausführung von CRUD (Create, Read, Update und Delete)-Operationen.

      Voraussetzungen

      Bevor Sie mit diesem Leitfaden beginnen, benötigen Sie Folgendes:

      Anmerkung: Dieses Tutorial verwendet für die Entwicklung einen macOS-Rechner. Wenn Sie ein anderes Betriebssystem verwenden, müssen Sie in den ersten Schritten möglicherweise sudo für npm-Befehle verwenden.

      Schritt 1 – Installieren der Adonis CLI

      In diesem Abschnitt werden Sie Adonis CLI und alle erforderlichen Pakete auf Ihrem lokalen Rechner installieren. Die CLI ermöglicht es Ihnen, ein neues AdonisJs-Projekt aufzubauen, sowie Boilerplates für Controller, Middlewares und Modelle in Ihrer Anwendung zu erstellen und zu generieren. Außerdem erstellen Sie Ihre Datenbank für das Projekt.

      Führen Sie den folgenden Befehl aus, um die AdonisJs CLI global auf Ihrem Rechner über npm zu installieren:

      Geben Sie nach Abschluss der Installation den folgenden Befehl im Terminal ein, um die Installation von AdonisJs zu bestätigen und die aktuelle Version anzuzeigen:

      Sie sehen eine Ausgabe, die die aktuelle Version von AdonisJs zeigt:

      Output

      4.1.0

      Mit der erfolgreichen Installation der AdonisJs CLI haben Sie nun Zugriff auf den Befehl adonis und können ihn zum Erstellen neuer Installationen eines AdonisJs-Projekts, zur Verwaltung Ihres Projekts und zum Erzeugen relevanter Dateien wie die Controller, Modelle usw. verwenden.

      Nun können Sie mit der Erstellung eines neuen AdonisJs-Projekts fortfahren, indem Sie den Befehl adonis wie hier gezeigt verwenden:

      • adonis new adonis-quotes-app

      Der vorhergehende Befehl erstellt eine Anwendung namens adonis-quotes-app​​​ in einem neuen Verzeichnis mit demselben Namen in Ihrem lokalen Projektverzeichnis mit der entsprechenden AdonisJs MVC-Struktur.

      Verschieben Sie sie in den neuen Anwendungsordner:

      Starten Sie dann Ihre Anwendung durch Ausführen von:

      Dadurch wird der Entwicklungsserver auf dem Standardport 3333 gestartet, wie in der Root-Datei .env für Ihre Anwendung angegeben. Navigieren Sie zu http://localhost:3333, um die Begrüßungsseite von AdonisJs anzuzeigen.

      Begrüßungsseite von AdonisJs

      Jetzt schließen Sie die Einrichtung Ihrer Datenbank ab. Hier installieren Sie den Treiber mysql, um sich über npm von Ihrer Node.js-Anwendung mit Ihrem MySQL-Server zu verbinden. Gehen Sie zunächst wieder zu Ihrem Terminal zurück, auf dem die Anwendung derzeit läuft, beenden Sie den Prozess mit STRG+C und führen Sie den folgenden Befehl aus:

      Nachdem Sie nun den MySQL Node.js-Treiber für diese Anwendung erfolgreich installiert haben, müssen Sie die Anwendungsdatenbank erstellen und die entsprechende Verbindung zu ihr aufbauen.

      Die neueste Version von MySQL, die Sie aus dem vorbereitendem Tutorial installiert haben, verwendet ein standardmäßiges Authentifizierungs-Plugin namens caching_ha2_password. Dieses wird derzeit von den Node.js-Treibern für MySQL nicht unterstützt. Um Probleme mit der Datenbankverbindung in Ihrer Anwendung zu vermeiden, müssen Sie einen neuen MySQL-Benutzer anlegen und das derzeit unterstützte Authentifizierungs-Plugin mysql_native_password verwenden.

      Um zu beginnen, greifen Sie mit dem Account root auf den MySQL-Client zu:

      Sie werden aufgefordert, das während der MySQL-Installation eingerichtete Passwort für den Account root einzugeben.

      Erstellen Sie als Nächstes den Benutzer und das Passwort unter Verwendung des Plugins mysql_native_password:

      • CREATE USER 'sammy'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

      Sie sehen den folgenden Output:

      Output

      Query OK, 0 rows affected (0.02 sec)

      Erstellen Sie nachfolgend eine Datenbank für die Anwendung mit:

      Sie sehen den folgenden Output:

      Output

      Query OK, 1 row affected (0.03 sec)

      Damit haben Sie die Datenbank für diese Anwendung erfolgreich erstellt.

      Aktivieren Sie jetzt den Zugriff auf die erstellte Datenbank für den neuen MySQL-Benutzer. Führen Sie den folgenden Befehl aus, um dem Benutzer alle Berechtigungen in der Datenbank zu erteilen:

      • GRANT ALL PRIVILEGES ON adonis.* TO 'sammy'@'localhost';

      Laden Sie die Berechtigungstabelle neu, indem Sie den folgenden Befehl ausführen, um die soeben vorgenommenen Änderungen zu übernehmen:

      Sie sehen den folgenden Output:

      Output

      Query OK, 0 rows affected (0.00 sec)

      Beenden Sie den MySQL-Client mit:

      Sie haben die AdonisJs CLI erfolgreich installiert, ein neues AdonisJs-Projekt erstellt und mysql über npm installiert.  Außerdem haben Sie die Datenbank für diese Anwendung erstellt und einen MySQL-Benutzer mit den entsprechenden Berechtigungen eingerichtet. Dies ist die Grundeinrichtung für Ihre Anwendung. Im nächsten Abschnitt beginnen Sie mit der Erstellung der erforderlichen Ansichten für Ihre Anwendung.

      Schritt 2 – Verwenden der Edge Templating Engine

      AdonisJs wird mit einer eigenen Template-Engine namens Edge bereitgestellt. Sie ermöglicht Ihnen die Erstellung einer wiederverwendbaren HTML-Vorlage und die Einführung von Front-End-Logik in Ihre Anwendung mit minimalem Code. Edge stellt JavaScript-Entwickler während der Entwicklung einer Anwendung die Werkzeuge zur Verfügung, um ein komponentenbasiertes Layout zu erstellen, Bedingungen zu schreiben, Iterationen zu verwenden und Anzeigeebenen zur Aufnahme der Logik zu erstellen. Alle Template-Dateien enden mit der Erweiterung .edge und werden im Verzeichnis resources/views gespeichert.

      Nachfolgend sind die Ansichten aufgeführt, die Ihre Anwendung für die ordnungsgemäße Funktionsweise benötigt:

      • Master Layout: Mit Edge können Sie eine Seite erstellen, die das CSS, gängige JavaScript-Dateien, jQuery und gängige Bestandteile der Benutzeroberfläche enthält, die in der gesamten Anwendung gleich bleiben, wie zum Beispiel die Navigationsleiste, das Logo, der Header usw. Sobald Sie die Seite Master Layout erstellt haben, übernehmen andere Ansichten (Seiten) in Ihrer Anwendung diese.
      • Index View: Diese Seite verwendet das Master Layout, um gängige Dateien zu erben und Inhalte für die Homepage der Anwendung darzustellen.
      • Login-Seite: Diese Seite verwendet ebenfalls das Master Layout und stellt das Formular mit den Eingabefeldern für Benutzername und Passwort für die Anmeldung der Benutzer dar.
      • Register-Seite: Hier sehen Benutzer ein Formular zur Registrierung und zur dauerhaften Speicherung der Informationen in der Datenbank.
      • Create Quote-Seite: Benutzer werden diese Seite zum Erstellen eines inspirierenden Zitats verwenden.
      • Edit Quote-Seite: Benutzer werden diese Seite zum Bearbeiten eines Zitats verwenden.
      • View Quote-Seite: Benutzer werden diese Seite zum Anzeigen eines bestimmten Zitats verwenden.

      Verwenden Sie zunächst den Befehl adonis, um die Seite Master Layout zu erstellen, indem Sie den folgenden Befehl ausführen:

      • adonis make:view layouts/master

      Sie werden eine Ausgabe ähnlich der folgenden sehen:

      Output

      ✔ create resources/views/layouts/master.edge

      Dieser Befehl erstellt automatisch eine Datei master.edge in Ihrem Ordner resources/views/layouts. Öffnen Sie die neue Datei:

      • nano resources/views/layouts/master.edge

      Fügen Sie den folgenden Code hinzu:

      /resources/views/layouts/master.edge

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <meta http-equiv="X-UA-Compatible" content="ie=edge">
          <title>adonis-quotes-app</title>
          {{ style('https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css') }}
          {{ style('style') }}
          {{ script('https://code.jquery.com/jquery-3.3.1.slim.min.js') }}
      </head>
      <body>
          <div class="container-fliud">
              @include('navbar')
              @!section('content')
          </div>
          {{ script('https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js') }}
      
      </body>
      </html>
      

      In dieser Datei schließen Sie die CDN-Dateien für Bootstrap CSS, Bootstrap JavaScript und jQuery ein. Sie fügen einen globalen CSS-Dateinamen style.css hinzu und schließen innerhalb der div eine Partiale-Datei namens navbar ein. Um Fragmente des HTML-Codes, die Sie auf mehreren Seiten Ihrer Anwendung benötigen, wie nav oder footer, wiederzuverwenden, können Sie Partiale einfügen. Dabei handelt es sich um kleinere Dateien, die den sich wiederholenden Code enthalten, sodass der Code für diese Elemente schneller an einer Stelle und nicht bei jedem Auftreten aktualisiert werden kann. Die navbar enthält Markierungen für die Schaltflächen Login und Register, ein Logo und einen Home-Link.

      Auf diese Weise können alle nachfolgenden für diese Anwendung erstellten Seiten das Master-Layout erweitern und die navbar rendern, ohne dass der Inhalt neu geschrieben werden muss. Sie erstellen diese Datei navbar später im Tutorial.

      Abschließend definieren Sie einen Abschnitt-Tag @! section(), um Inhalte anderer Seiten einzuschließen und sie vom Master-Layout rendern zu lassen. Damit dies wie erwartet funktioniert, müssen alle neuen Seiten, die das Master-Layout erweitern, auch einen Abschnitt-Tag mit demselben Namen (d. h. @section('content')) definieren.

      Speichern und schließen Sie die Datei, sobald Sie mit der Bearbeitung fertig sind.

      Als Nächstes verwenden Sie den Befehl adonis, um die Navigationsleiste zu erstellen:

      Sie sehen einen Output, der so ähnlich wie der nachfolgende aussieht:

      Output

      ✔ create resources/views/navbar.edge

      Öffnen Sie die neu erstellte Datei:

      • nano resources/views/navbar.edge

      Fügen Sie dann den folgenden Code hinzu:

      /resources/views/navbar.edge

      <nav class="navbar navbar-expand-lg navbar-dark text-white">
          <a class="navbar-brand" >LOGO</a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
          </button>
      
          <div class="collapse navbar-collapse" id="navbarNav">
              <ul class="navbar-nav">
                  <li class="nav-item active ">
                      <a class="btn text-white" href="https://www.digitalocean.com/">Home</a>
                  </li>
              </ul>
          </div>
          <div class="navbar-right" id="navbarNav">
              @loggedIn
                  <ul class="navbar-nav">
                          <li>
                              <div class="text-right">
                                   <a href="{{route('create.quote')}}" class="btn btn-outline-primary">Create Quote</a>
                              </div>
                          </li>
      
                      <li class="nav-item dropdown">
                          <a class="nav-link dropdown-toggle" href="https://www.digitalocean.com/#" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                             {{ auth.user.username}}
                          </a>
                          <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
                              <form method="POST" action="{{route('logout')}}">
                                  {{ csrfField() }}
                                    <button  type="submit" class="dropdown-item" href="">logout</button>
                              </form>
                          </div>
                      </li>
                  </ul>
              @else
                  <ul class="navbar-nav">
                      <li class="nav-item active pr-2">
                          <a href="{{route('login.create')}}" class="btn btn-outline-danger">
                            login
                          </a>
                      </li>
                      <li class="nav-item active pr-2">
                          <a href="{{route('register.create')}}" class="btn btn-outline-primary">
                              Register
                          </a>
                      </li>
                  </ul>
              @endloggedIn
          </div>
      </nav>
      

      Zusätzlich zur Definition der Links zu der Homepage und einer Schaltfläche zum Registrieren und Anmelden fügen Sie ein Tag @loggedIn hinzu. Damit können Sie eine bedingte Anweisung um den authentifizierten Benutzer herum schreiben und bei Bedarf entsprechende Inhalte anzeigen. Für einen authentifizierten Benutzer zeigt die Anwendung den Benutzernamen und eine Schaltfläche zum Erstellen eines neuen Zitats an. Wenn ein Benutzer nicht angemeldet ist, zeigt Ihre Anwendung eine Schaltfläche für die Anmeldung oder die Registrierung an. Diese Seite wird als Partiale auf jeder Seite eingefügt, so wie es zuvor im Master-Layout für diese Anwendung enthalten war.

      Speichern und schließen Sie die Datei.

      Nun erstellen Sie die Indexseite, die Sie für die Homepage der Anwendung verwenden. Sie wird die Liste aller inspirierenden Zitate, die die Benutzer schreiben, rendern und anzeigen:

      Sie werden eine Ausgabe sehen, die der folgenden ähnelt:

      Output

      ✔ create resources/views/index.edge

      Die hier erstellte Datei wird sich in resources/views/index.edge befinden. Öffnen Sie die Datei:

      • nano resources/views/index.edge

      Fügen Sie dann den folgenden Code hinzu:

      /resources/views/index.edge

      @layout('layouts/master')
      @section('content')
      
      <div class="container">
          <div class="text-center">
              @if(flashMessage('successmessage'))
                  <span class="alert alert-success p-1">{{ flashMessage('successmessage') }}</span>
              @endif
          </div>
          <div class="row">
              @each(quote in quotes)
                  <div class="col-md-4 mb-4 quote-wrapper">
                      <a href="https://www.digitalocean.com/view-quote/{{quote.id}}" class="w-100">
                          <div class="card shadow-lg bg-dark text-white">
                              <div class="card-body">
                                  <blockquote class="blockquote mb-0">
                                      <p>{{quote.body}}</p>
                                      <footer class="blockquote-footer">
                                          <cite title="Source Title"> {{quote.username}}</cite>
                                      </footer>
                                  </blockquote>
                                  @if(auth.user.id == quote.user_id)
                                    <div>
                                      <a  href="http://www.digitalocean.com/edit-quote/{{quote.id}}" class="btn btn-primary">edit</a>
                                      <a href="http://www.digitalocean.com/delete-quote/{{quote.id}}" class="btn btn-danger">delete</a>
                                    </div>
                                  @endif
                              </div>
                          </div>
                      </a>
                  </div>
              @else
               <div class="col-md-12 empty-quote text-center">
                      <p>No inspirational quote has been created</p>
               </div>
              @endeach
          </div>
      </div>
      @endsection
      

      Hier geben Sie an, dass diese Ansicht das Layout master durch Erweiterung verwenden wird. Diese Seite hat nun Zugriff auf alle Bibliotheken, Stylesheets und die navbar, die im Layout master enthalten sind. Als Nächstes iterieren Sie über ein Array von quotes (Zitaten) unter Verwendung des integrierten Tags @each. Das Array quotes wird von dem QuoteController, den Sie später in diesem Tutorial erstellen werden, an diese Ansicht übergeben. Wenn keine Zitate vorhanden sind, wird eine entsprechende Meldung angezeigt.

      Speichern und schließen Sie diese Datei.

      Um die Anmeldeseite zu erstellen, führen Sie nun den folgenden Befehl aus:

      • adonis make:view auth/login

      Sie werden eine Ausgabe sehen, die der folgenden ähnelt:

      Output

      ✔ create resources/views/auth/login.edge

      Dadurch wird automatisch ein Ordner auth innerhalb von resources/views erstellt und darin ebenfalls eine Datei login.edge angelegt. Öffnen Sie die Datei login.edge:

      • nano resources/views/auth/login.edge

      Fügen Sie den folgenden Inhalt hinzu:

      /resources/views/auth/login.edge

      @layout('layouts/master')
      @section('content')
        <div class="container">
          <div class="row">
            <div class="col-md-4 shadow bg-white mt-5 rounded offset-md-4">
              <form method="POST" action="{{route('login.store')}}">
                {{ csrfField() }}
                  <div>
                    @if(flashMessage('successmessage'))
                      <span class="alert alert-success p-1">{{ flashMessage('successmessage') }}</span>
                    @endif
                  </div>
                  <div class="form-group">
                    <label for="email">Email address</label>
                    <input type="email" class="form-control" id="email" name="email" value="{{old('email','')}}"  placeholder="Enter email">
                    {{ elIf('<span class=text-danger>$self</span>', getErrorFor('email'), hasErrorFor('email')) }}
                  </div>
                  <div class="form-group">
                    <label for="pasword">Password</label>
                    <input type="password" class="form-control" id="password" name="password" value="{{old('password','')}}" placeholder="Password">
                    {{ elIf('<span class=text-danger>$self</span>', getErrorFor('password'), hasErrorFor('password')) }}
                  </div>
      
                  <div class="text-center">
                    <button type="submit" class="btn btn-primary">Submit</button>
                  </div>
              </form>
            </div>
          </div>
        </div>
      @endsection
      

      Diese Datei enthält ein Formular, das Eingabeelemente enthält,mit denen Sie den Benutzernamen und das Passwort eines registrierten Benutzers erfassen, bevor dieser sich erfolgreich authentifizieren und mit der Erstellung von Zitaten beginnen kann. Ein weiteres wichtiges Element, das Sie auf dieser Seite beachten sollten, ist das {{ csrfField() }}​​​. Es handelt sich dabei um eine globale Variable, die AdonisJs verwendet, um das CSRF-Zugriffstoken zu übergeben, wenn eine POST-, PUT– und DELETE-Anfrage aus Ihrer Anwendung gesendet wird.

      Dies wurde eingerichtet, um Ihre Anwendung vor Cross-Site Request Forgery (CSRF)-Angriffen zu schützen. Es funktioniert, indem es für jeden Benutzer, der Ihre Website besucht, ein eindeutiges CSRF-Geheimnis erzeugt. Sobald Ihre Benutzer eine HTTP-Anfrage vom Frontend aus senden, wird ein entsprechendes Token für dieses Geheimnis generiert und mit der Anfrage weitergegeben. Auf diese Weise kann die für diese Anfrage innerhalb von AdonisJs erstellte Middleware überprüfen, ob sowohl das Token als auch das CSRF-Geheimnis gültig sind und dem aktuell authentifizierten Benutzer gehören.

      Speichern und schließen Sie die Datei, sobald Sie fertig sind.

      Als Nächstes erstellen Sie die Registrierungsseite mit diesem Befehl:

      • adonis make:view auth/register

      Sie werden eine ähnliche Ausgabe wie diese sehen:

      Output

      ✔ create resources/views/auth/register.edge

      Suchen und öffnen Sie die neu erstellte Datei in resources/views/auth/register.edge:

      • nano resources/views/auth/register.edge

      Fügen Sie folgenden Code hinzu:

      resources/views/auth/register.edge

      @layout('layouts/master')
      @section('content')
        <div class="container ">
          <div class="row">
              <div class="col-md-4  bg-white p-3 mt-5 shadow no-border rounded offset-md-4">
                <form method="POST" action="{{route('register.store')}}">
                  {{ csrfField() }}
                    <div class="form-group">
                      <label for="name">Fullname</label>
                      <input type="text" class="form-control" id="name" name="name"  value="{{old('name','')}}" placeholder="Enter Fullname">
                      {{ elIf('<span class=text-danger>$self</span>', getErrorFor('name'), hasErrorFor('name')) }}
                    </div>
                    <div class="form-group">
                      <label for="email">Email address</label>
                      <input type="email" class="form-control" id="email"  name="email" value="{{old('email','')}}" placeholder="Enter email">
                      {{ elIf('<span class=text-danger>$self</span>', getErrorFor('email'), hasErrorFor('email')) }}
                    </div>
                    <div class="form-group">
                      <label for="pasword">Password</label>
                      <input type="password" class="form-control" id="password" name="password" placeholder="Password">
                      {{ elIf('<span class=text-danger>$self</span>', getErrorFor('password'), hasErrorFor('password')) }}
                    </div>
                    <div class="text-center">
                        <button type="submit" class="btn btn-primary">Submit</button>
                    </div>
                </form>
              </div>
          </div>
        </div>
      @endsection
      

      Ähnlich wie die Anmeldeseite enthält diese Datei ein HTML-Formular mit Eingabefeldern zur Erfassung von Name, E-Mail und Passwort eines Benutzers während des Registrierungsvorgangs. Ebenfalls enthalten ist das {{ csrfField() }}, da es für jede Post-Anfrage für eine AdonisJs-Anwendung erforderlich ist.

      Speichern und schließen Sie die Datei.

      Nun erstellen Sie eine neue Datei, um ein inspirierendes Zitat zu erstellen, indem Sie den folgenden Befehl vom Terminal aus ausführen:

      • adonis make:view quotes/create-quote

      Sie sehen eine Ausgabe wie:

      Output

      ✔ create resources/views/quotes/create-quote.edge

      Öffnen Sie resources/views/quotes/create-quote.edge:

      • nano resources/views/quotes/create-quote.edge

      Und fügen Sie den folgenden Inhalt hinzu:

      /resources/views/quotes/create-quote.edge

      @layout('layouts/master')
      @section('content')
      <div class="container">
          <div class="row">
              <div class="col-md-3"></div>
              <div class="col-md-6 shadow bg-white mt-5 rounded p-3">
                  <div class="float-right">
                      <a href="https://www.digitalocean.com/" class="btn btn-outline-dark ">back</a>
                  </div>
                      <br>
      
                  <div class="clear-fix"></div>
                      <form method="POST" action="{{route('store.quote')}}">
                          {{ csrfField() }}
                          <div class="form-group">
                              <label for="quote">Create Quote</label>
                              <textarea type="text" rows="5"  name='body' id="body" class="form-control" id="quote" placeholder="Write an inspirational quote"></textarea>
                          </div>
      
                          <div class="text-center">
                              <button type="submit" class="btn btn-primary">Submit</button>
                          </div>
                      </form>
                  </div>
              </div>
              <div class="col-md-3"></div>
          </div>
      </div>
      @endsection
      

      Diese Seite erweitert das Master-Layout und enthält ein HTML-Formular mit einem Textbereichselement, das es dem Benutzer ermöglicht, Text über mehrere Zeilen einzugeben, bevor er über die entsprechende Route veröffentlicht und gehandhabt wird.

      Speichern und schließen Sie die Datei, sobald Sie fertig sind.

      Erstellen Sie als Nächstes eine Seite zur Bearbeitung eines bestimmten Zitats. Führen Sie den folgenden Befehl vom Terminal aus:

      • adonis make:view quotes/edit-quote

      Sie sehen den folgenden Output:

      Output

      ✔ create resources/views/quotes/edit-quote.edge

      Öffnen Sie die Datei mit:

      • nano resources/views/quotes/edit-quote.edge

      Fügen Sie den folgenden Inhalt zu resources/views/quotes/edit-quote hinzu:

      /resources/views/quotes/edit-quote.edge

      @layout('layouts/master')
      @section('content')
      <div class="container">
          <div class="row">
              <div class="col-md-6 shadow bg-white rounded p-3 offset-md-3">
                  <div class="float-right">
                      <a href="https://www.digitalocean.com/" class="btn btn-outline-dark ">back</a>
                  </div>
                  <br>
      
                  <div class="clear-fix"></div>
                  <form method="POST" action="/update-quote/{{quote.id}}">
                      {{ csrfField() }}
                      <div class="form-group">
                          <label for="pasword">Edit Quote</label>
                          <textarea type="text" rows="5"  name='body' id="body" class="form-control" id="quote" placeholder="write the inspirational quote">{{quote.body}}</textarea>
                      </div>
                      <div class="text-center">
                          <button type="submit" class="btn btn-primary">Update</button>
                      </div>
      
                  </form>
              </div>
          </div>
      </div>
      @endsection
      

      Diese Seite enthält ähnliche Inhalte wie die Datei create-quote.edge – der Unterschied besteht darin, dass sie die Details eines bestimmten Zitats enthält, die zu bearbeiten sind, <form method="POST" action="/update-quote/{{quote.id}}">.

      Speichern und schließen Sie die Datei.

      Erstellen Sie abschließend eine Seite, um ein einzelnes inspirierendes Zitat anzuzeigen:

      • adonis make:view quotes/quote

      Sie sehen eine Ausgabe, die dieser ähnelt:

      Output

      ✔ create resources/views/quotes/quote.edge

      Öffnen Sie die Datei mit:

      • nano resources/views/quotes/quote.edge

      Fügen Sie folgenden Code hinzu:

      /resources/views/quotes/quote.edge

      @layout('layouts/master')
      @section('content')
      <div class="container">
          <div class="row">
              <div class="col-md-6 offset-md-3">
                  <div class="card shadow-lg bg-dark text-white">
                      <div class="card-body">
                          <div class="float-right">
                              <a href="https://www.digitalocean.com/" class="btn btn-outline-primary ">back</a>
                          </div>
                              <br>
                          <div class="clear-fix"></div>
                          <blockquote class="blockquote mb-0">
                              <p>{{quote.body}}</p>
                              <footer class="blockquote-footer">
                                  <cite title="Source Title">{{quote.username}}</cite>
                              </footer>
                          </blockquote>
                      </div>
                  </div>
              </div>
          </div>
      </div>
      @endsection
      

      Diese Seite gibt die Details eines bestimmten Zitats wieder, einschließlich des Zitatkörpers quote.body, und des Autors, der es erstellt hat, quote.username.

      Wenn Sie mit der Datei fertig sind, speichern und schließen Sie sie.

      Sie haben alle erforderlichen Seiten für Ihre Anwendung mit Hilfe der Edge Templating Engine erstellt. Als Nächstes konfigurieren und stellen Sie eine Verbindung mit der Datenbank Ihrer Anwendung her.

      Schritt 3 – Erstellen eines Datenbankschemas

      Wenn Sie Ihre Anwendung jetzt bereitstellen, wird ein Fehler ausgegeben, da Sie die Anwendung noch mit einer Datenbank verbinden müssen. In diesem Abschnitt richten Sie eine Verbindung mit der Datenbank ein und verwenden dann den Befehl adonis zur Erzeugung einer Migrationsdatei, die zur Erstellung der Tabellen für die Datenbank verwendet wird.

      AdonisJs enthält ein ORM namens Lucid ORM, das eine aktive Datensatz-Implementierung für die Arbeit mit Ihrer Datenbank bereitstellt. Damit entfällt das mühsame Schreiben von SQL-Abfragen, die Daten in Echtzeit aus der Datenbank abrufen. Dies ist besonders hilfreich bei der Arbeit an einer komplexen Anwendung, die viele Abfragen erfordert. So können Sie beispielsweise alle Zitate aus Ihrer Anwendung abrufen, indem Sie eingeben:

      const quotes = await Quote.all()
      

      Um mit der entsprechenden Konfiguration für Ihre Anwendungsdatenbank fortzufahren, stellen Sie sicher, dass Sie sich immer noch im Stammverzeichnis Ihrer Anwendung befinden und erstellen Sie eine .env-Datei:

      Öffnen Sie die neu erstellte Datei und fügen Sie den folgenden Inhalt hinzu:

      .env

      HOST=127.0.0.1
      PORT=3333
      NODE_ENV=development
      APP_URL=http://${HOST}:${PORT}
      CACHE_VIEWS=false
      APP_KEY=bTVOEgUvmTCfkvgrK8gEBC3Qxt1xSYr0
      DB_CONNECTION=mysql
      DB_HOST=127.0.0.1
      DB_PORT=3306
      DB_USER=sammy
      DB_PASSWORD=password
      DB_DATABASE=adonis
      SESSION_DRIVER=cookie
      HASH_DRIVER=bcrypt
      

      Standardmäßig ist die Datenbankverbindung für eine AdonisJs-Anwendung SQLite, die Sie hier auf MySQL aktualisieren werden. Des Weiteren geben Sie den PORT für die Anwendung, die Anwendungsumgebung und Berechtigungsnachweise für die Datenbank an. Stellen Sie sicher, dass Sie die Platzhalter DB_USER, DB_PASSWORD und DB_DATABASE mit Ihren Berechtigungsnachweisen ersetzen.

      Als Nächstes erstellen Sie das Modell und eine Migrationsdatei für Quote unter Verwendung der Adonis CLI. Führen Sie dazu den folgenden Befehl aus:

      • adonis make:model Quote --migration

      Sie sehen eine Ausgabe, die der folgenden ähnelt:

      Output

      ✔ create app/Models/Quote.js ✔ create database/migrations/1568209992854_quote_schema.js

      Dieser Befehl erstellt ein Modell für Quote im Ordner app/Models und eine Schemadatei in dem Ordner database/migrations. Der neu erstellten Schemadatei wird der aktuelle Zeitstempel vorangestellt. Öffnen Sie die Schemadatei mit:

      • nano database/migrations/1568209992854_quote_schema.js

      Aktualisieren Sie den Inhalt mit dem folgenden Code:

      database/migrations/…quote_schema.js

      'use strict'
      /** @type {import('@adonisjs/lucid/src/Schema')} */
      const Schema = use('Schema')
      class QuoteSchema extends Schema {
        up () {
          this.create('quotes', (table) => {
            table.increments()
            table.integer('user_id').notNullable()
            table.string('username', 80).notNullable()
            table.string('body').notNullable()
            table.timestamps()
          })
        }
        down () {
          this.drop('quotes')
        }
      }
      module.exports = QuoteSchema
      

      Eine Schemadatei in AdonisJs erfordert zwei verschiedene Methoden:

      • up: Wird verwendet, um eine neue Tabelle zu erstellen oder eine bestehende Tabelle zu ändern.
      • down: Wird verwendet, um die bei der Methode up vorgenommene Änderung rückgängig zu machen.

      Zusätzlich zu den Feldern timestamps() und increments() aktualisieren Sie den Inhalt der Schemadatei mit den Feldattributen user_id, username und dem body eines zu erstellenden Zitats. Die Felder user_id und username verweisen auf die Details des Benutzers, der ein bestimmtes Zitat erstellt. Dies definiert eine 1:n-Beziehung und bedeutet, dass ein Benutzer eine unendliche Anzahl von Zitaten besitzen kann, während ein einzelnes Zitat nur einem Benutzer gehören kann.

      Speichern und schließen Sie die Datei.

      AdonisJs wird standardmäßig mit Modell User und der zugehörigen Migrationsdatei installiert. Dies erfordert nur eine kleine Änderung, um die Beziehung zwischen dem Modell User und Quote herzustellen.

      Öffnen Sie das Modell User in app/Models/User.js:

      Fügen Sie diese Methode unmittelbar nach der Methode tokens() hinzu:

      app/Models/User.js

      ...
      class User extends Model {
        ...
        tokens () {
          return this.hasMany('App/Models/Token')
        }
      
        quote () {
          return this.hasMany('App/Models/Quote')
        }
      }
      
      module.exports = User
      

      Dadurch wird eine 1:n-Beziehung mit der Tabelle Quote unter Verwendung von user_id als Fremdschlüssel festgelegt.

      Speichern und schließen Sie die Datei.

      Um diesen Abschnitt abzuschließen, verwenden Sie den folgenden Befehl zur Ausführung von Migrationen, wodurch die Methode up() aller Migrationsdateien ausgeführt wird:

      Sie werden eine Ausgabe sehen, die der folgenden ähnelt:

      Output

      migrate: 1503248427885_user.js migrate: 1503248427886_token.js migrate: 1568209992854_quote_schema.js Database migrated successfully in 3.42 s

      Sie haben eine Verbindung mit Ihrer Datenbank konfiguriert und gesichert. Des Weiteren haben Sie ein Modell Quote und die dazugehörige Schemadatei erstellt und eine 1:n-Beziehung zwischen User und Quote hergestellt. Als Nächstes generieren Sie die Routen und Controller zur Bearbeitung von HTTP-Anfragen und die Geschäftslogik zum Erstellen, Bearbeiten und Löschen eines inspirierenden Zitats.

      Schritt 4 – Erstellen von Controllern und Einrichten von Routen

      In diesem Abschnitt beginnen Sie mit der Erstellung von Controllern, die die gesamte Logik für die Anwendung verarbeiten und diese später an eine bestimmte Route anhängen, damit die Benutzer über eine URL darauf zugreifen können.

      Zu Beginn werden Sie mit der Adonis CLI einen neuen HTTP-Request-Controller erstellen, der alle Authentifizierungsprozesse für Ihre Anwendung verarbeiten kann, indem Sie den folgenden Befehl ausführen:

      • adonis make:controller Auth --type http

      Dieser Befehl erstellt eine Datei AuthController.js und speichert sie innerhalb des Ordners app/Controllers/Http. Sie verwenden das Flag --type, um anzugeben, dass dieser Controller ein HTTP-Controller sein soll.

      Sie werden eine Ausgabe sehen, die der folgenden ähnelt:

      Output

      ✔ create app/Controllers/Http/AuthController.js

      Öffnen Sie als Nächstes die neu erstellte Controller-Datei:

      • nano app/Controllers/Http/AuthController.js

      Aktualisieren Sie sie mit dem folgenden Inhalt:

      app/Controllers/Http/AuthController.js

      'use strict'
      const User = use('App/Models/User')
      class AuthController {
      
          loginView({ view }) {
              return view.render('auth.login')
          }
          registrationView({ view }) {
              return view.render('auth.register')
          }
      
          async postLogin({ request, auth, response}) {
              await auth.attempt(request.input('email'), request.input('password'))
              return response.route('index')
          }
      
          async postRegister({ request, session, response }) {
              const user = await User.create({
                  username: request.input('name'),
                  email: request.input('email'),
                  password: request.input('password')
              })
              session.flash({ successmessage: 'User have been created successfully'})
              return response.route('login.create');
          }
      
          async logout ({ auth, response }) {
              await auth.logout()
              return response.route("https://www.digitalocean.com/")
          }
      }
      module.exports = AuthController
      

      In dieser Datei importieren Sie das Modell User und erstellen dann zwei Methoden namens loginView() und registerView(), um die Anmelde- bzw. Registrierungsseite zu rendern. Abschließend erstellen Sie die folgenden asynchronen Methoden:

      • postLogin(): Mit dieser Methode erhalten Sie den Wert von email und password, die mit Hilfe der in AdonisJs eingebauten Methode request gepostet wurden, und validieren dann diesen Benutzer anhand der Details in der Datenbank. Wenn ein solcher Benutzer in der Datenbank existiert und den korrekten Berechtigungsnachweis eingegeben hat, wird er zurück auf die Homepage umgeleitet und authentifiziert, bevor er ein neues Zitat erstellen kann. Andernfalls wird eine Meldung angezeigt, die auf den falschen Berechtigungsnachweis hinweist.
      • postRegister(): Dies erhält den Wert von username, email und password für einen Benutzer, um ein Konto für diesen Benutzer in der Datenbank zu erstellen. Eine Nachricht mit der Angabe, dass dieser Benutzer erfolgreich erstellt wurde, wird an die Sitzung weitergeleitet, und der Benutzer wird auf die Anmeldeseite umgeleitet, um authentifiziert zu werden und mit der Erstellung eines Zitats zu beginnen.
      • logout(): Diese Methode verarbeitet die Logout-Funktionalität und leitet den Benutzer zurück auf die Homepage.

      Speichern und schließen Sie die Datei.

      Nachdem Sie nun den Controller für die Registrierung und Authentifizierung von Benutzern eingerichtet haben, fahren Sie mit der Erstellung eines HTTP-Request-Controllers für die Verwaltung aller Operationen in Bezug auf Zitate fort.

      Führen Sie, zurück im Terminal, den folgenden Befehl aus, um den QuoteController zu erstellen:

      • adonis make:controller Quote --type http --resource

      Die Verwendung des Flags --resource erstellt einen Controller mit vordefinierten kreativen Methoden zur Durchführung von CRUD (Create, Read, Update und Delete)-Operationen.

      Sie sehen:

      Output

      ✔ create app/Controllers/Http/QuoteController.js

      Finden Sie diese Datei innerhalb von app/Controllers/Http/QuoteControllers.js:

      • nano app/Controllers/Http/QuoteController.js

      Aktualisieren Sie sie mit dem folgenden Inhalt:

      app/Controllers/Http/QuoteController.js

      'use strict'
      const Quote = use('App/Models/Quote')
      
      class QuoteController {
      
        async index ({ view }) {
          const quote = await Quote.all()
          return view.render('index', {
            quotes: quote.toJSON()
          })
        }
      
        async create ({ view }) {
          return view.render('quotes.create-quote')
        }
      
        async store ({ request,auth,session, response }) {
          const quote = await Quote.create({
            user_id: auth.user.id,
            username: auth.user.username,
            body: request.input('body')
          })
          session.flash({ 'successmessage': 'Quote has been created'})
          return response.redirect("https://www.digitalocean.com/")
        }
      
        async show ({ params, view }) {
          const quote = await Quote.find(params.id)
          return view.render('quotes.view-quote', {
            quote: quote.toJSON()
          })
        }
      
        async edit ({ params, view }) {
          const quote = await Quote.find(params.id)
          return view.render('quotes.edit-quote', {
            quote: quote.toJSON()
          })
        }
      
        async update ({ params, request, response, session }) {
          const quote = await Quote.find(params.id)
          quote.body = request.input('body')
          await quote.save()
          session.flash({'successmessage': 'Quote has been updated'})
          return response.redirect("https://www.digitalocean.com/")
        }
      
        async destroy ({ params, response, session }) {
          const quote = await Quote.find(params.id)
          await quote.delete()
          session.flash({'successmessage': 'Quote has been deleted'})
          return response.redirect("https://www.digitalocean.com/")
        }
      }
      module.exports = QuoteController
      

      In diesem Controller haben Sie das Modell Quote importiert und die folgenden Methoden aktualisiert, die automatisch mit AdonisJs CLI erstellt wurden:

      • index(): Um alle Zitate aus der Datenbank zu holen und sie auf der Homepage der Anwendung darzustellen.
      • create(): Um eine Seite zum Erstellen von Zitaten darzustellen.
      • store(): Um ein neu erstelltes Zitat dauerhaft in der Datenbank zu speichern und eine entsprechende Antwort zurückzugeben.
      • show(): Um die id eines bestimmten Zitats zu erhalten, es aus der Datenbank abzurufen und auf der Seite zur Bearbeitung von Zitaten anzuzeigen.
      • edit(): Um Details eines bestimmten Zitats aus der Datenbank abzurufen und zur Bearbeitung darzustellen.
      • update(): Um jede Aktualisierung eines Zitats zu verarbeiten und den Benutzer zurück auf die Homepage zu leiten.
      • destroy(): Um ein bestimmtes Zitat zu löschen und vollständig aus der Datenbank zu entfernen.

      Speichern und schließen Sie die Datei.

      Nachdem Sie alle erforderlichen Controller für diese Anwendung erstellt haben, können Sie nun die Routen so einrichten, dass die Benutzer problemlos mit Ihrer Anwendung interagieren können. Navigieren Sie zu Beginn zu der Datei start/routes.js:

      Ersetzen Sie den Inhalt mit dem folgenden:

      start/routes.js

      'use strict'
      const Route = use('Route')
      
      Route.get("https://www.digitalocean.com/",'QuoteController.index').as('index')
      Route.get('/register','AuthController.registrationView').as('register.create')
      Route.post('/register-store','AuthController.postRegister').as('register.store').validator('Register')
      Route.get('/login','AuthController.loginView').as('login.create')
      Route.post('/login-store','AuthController.postLogin').as('login.store')
      Route.get('/view-quote/:id','QuoteController.show').as('view.quote')
      
      Route.group(() => {
          Route.get('/create-quote','QuoteController.create').as('create.quote')
          Route.post('/store-quote','QuoteController.store').as('store.quote')
          Route.get('/edit-quote/:id','QuoteController.edit').as('edit.quote')
          Route.post('/update-quote/:id','QuoteController.update').as('update.quote')
          Route.get('/delete-quote/:id','QuoteController.destroy').as('delete.quote')
          Route.post('/logout','AuthController.logout').as('logout')
      }).middleware(['auth'])
      

      Hier definieren Sie den Pfad für jede Route in Ihrer Anwendung, geben die HTTP-Verben für jede Aktion an und binden die Route an eine bestimmte Methode in jedem Controller. Außerdem benennen Sie jede dieser Routen so, wie sie in den Controllern und Ansichten referenziert wurden.

      Um sicherzustellen, dass nur authentifizierte Benutzer auf alle Zitatrouten zugreifen können, ordnen Sie eine Gruppe namens Middleware zu. Schließlich hängen Sie eine Validierer-Methode an die Route register.store an, um die Benutzereingaben zu validieren.

      Speichern und schließen Sie die Datei.

      Sie haben Ihre Controller erstellt und die Routen für Ihre Anwendung eingerichtet. Als Nächstes erstellen Sie die in diesem Schritt definierte Validierer-Methode.

      Schritt 5 – Validieren der Benutzereingabe

      Standardmäßig verfügt AdonisJs nicht über integrierte Validierer. Daher müssen Sie den Validierer für Ihre Anwendung manuell installieren und registrieren.

      Führen Sie den folgenden Befehl aus, um ihn zu installieren:

      Öffnen Sie die folgende Datei, um den Anbieter des Validieres (Validator Provider) zu registrieren:

      Registrieren Sie dann den Anbieter des Validieres, indem Sie ihn an die Liste der Anbieter anhängen, wie nachfolgend gezeigt:

      start/app.js

      ...
      const providers = [
         ...
         '@adonisjs/cors/providers/CorsProvider',
         '@adonisjs/shield/providers/ShieldProvider',
         '@adonisjs/session/providers/SessionProvider',
         '@adonisjs/auth/providers/AuthProvider',
         '@adonisjs/validator/providers/ValidatorProvider'
      ]
      

      Nachdem Sie den Anbieter des Validieres innerhalb Ihrer Anwendung installiert und registriert haben, erstellen Sie nun mit dem folgenden Befehl einen benutzerdefinierten Validierer, um die Eingabe des Benutzers während der Registrierung zu validieren:

      • adonis make:validator Register

      Dadurch wird eine Datei Register.js im Verzeichnis App/Validators erstellt. Öffnen Sie die Datei mit:

      • nano app/Validators/Register.js

      Fügen Sie den folgenden Code zur Datei hinzu:

      app/Validators/Register.js

      'use strict'
      class Register {
        get rules () {
          return {
            name:'required',
            email:'required|email|unique:users',
            password:'required|min:8'
          }
        }
      
        get messages(){
          return{
            'name.required':'Full name is required',
            'email.required':'email is required',
            'email.unique':'email already exists',
            'password.required':'password is required',
            'password.min':'password should be at least 8 characters'
          }
        }
      }
      module.exports = Register
      

      In Ihrer Anwendung definieren Sie Regeln für bestimmte Felder. Wenn Validierungen zu einem beliebigen Zeitpunkt fehlschlagen, setzt der Validierer den Fehler automatisch als Flash-Meldung und der Benutzer wird zurück zum Formular geleitet.

      Speichern und schließen Sie die Datei, sobald Sie die Bearbeitung abgeschlossen haben.

      Um Ihrer Anwendung ein Design hinzuzufügen, öffnen Sie schließlich die folgende Datei:

      Ersetzen Sie die Inhalte mit dem folgenden:

      /public/style.css

      @import url('https://fonts.googleapis.com/css?family=Montserrat:300');
      
      html, body {
        height: 100%;
        width: 100%;
      }
      
      body {
        font-family: 'Montserrat', sans-serif;
        font-weight: 300;
        background-image: url("/splash.png");
        background-color: #220052;
      }
      
      * {
        margin: 0;
        padding: 0;
      }
      
      a {
        color: inherit;
        text-decoration: underline;
      }
      
      p {
        margin: 0.83rem 0;
      }
      
      .quote-wrapper {
        margin-top: 20px;
      }
      
      .quote-wrapper a {
        text-decoration: none;
      }
      
      .quote-wrapper a:hover {
        color: #ffffff;
      }
      
      .empty-quote {
        color: #ffffff;
      }
      
      form {
        padding: 20px;
      }
      

      In dieser Datei aktualisieren Sie das CSS-Design Ihrer Anwendung in der Datei style.css.

      Sie haben einen Anbieter eines Validieres installiert und registriert, um die Eingabe von Benutzern während des Registrierungsvorgangs zu überprüfen. Des Weiteren haben Sie den Inhalt Ihres Stylesheets aktualisiert, um der Anwendung mehr Design hinzuzufügen. Im letzten Schritt testen Sie Ihre Anwendung.

      Schritt 6 – Bereitstellen der Anwendung

      In diesem Schritt stellen Sie Ihre Anwendung bereit und erstellen einen Benutzer und ein Passwort zum Testen der Authentifizierung. Außerdem fügen Sie Ihrer Anwendung ein Zitate hinzu und zeigen es auf der Homepage an.

      Um Ihre Anwendung zu testen, starten Sie den Entwicklungsserver mit dem folgenden Befehl aus dem Stammverzeichnis Ihrer Anwendung:

      Dadurch wird die Anwendung auf dem innerhalb der Stammdatei .env definierten Port, 3333, gestartet. Navigieren Sie in Ihrem Browser zu http://localhost:3333.

      Homepage der Zitat-Anwendung

      Die Homepage ist zur Zeit leer, da Sie keine Zitate erstellt haben. Klicken Sie auf die Schaltfläche Register.

      Anmeldeseite

      Geben Sie Ihre Informationen ein und klicken Sie auf die Schaltfläche Submit, um den Registrierungsvorgang abzuschließen. Sie werden auf die Anmeldeseite umgeleitet. Geben Sie Ihre E-Mail-Adresse und Ihr Passwort für die Authentifizierung ein.

      Anmeldeseite

      Nachdem Sie authentifiziert sind, klicken Sie auf die Schaltfläche Create Quote.

      Seite „Zitat erstellen“

      Geben Sie ein Zitat ein und navigieren Sie zu der Seite View all, um Ihr Zitat anzuzeigen.

      Seite „Alle Zitate anzeigen“

      Sie haben Ihre Anwendung getestet, indem Sie einen Benutzer erstellt und authentifiziert und anschließend ein Zitat verfasst haben.

      Zusammenfassung

      In diesem Tutorial haben Sie eine Webanwendung mit AdonisJs erstellt. Sie haben die Anwendung unter Verwendung der AdonisJs CLI eingerichtet und die CLI zum Erstellen anderer relevanter Dateien wie Controller, Modelle und Ansichten verwendet.

      Sie können Webanwendungen mit diesem Framework unabhängig von Ihrer Größe und Komplexität erstellen. Sie können den Quellcode für dieses Projekt hier auf GitHub herunterladen. Um weitere Funktionen zu erkunden, können Sie auch die offizielle Dokumentation besuchen.

      Wenn Sie einige unserer anderen JavaScript-Framework-Tutorials erkunden möchten, sehen Sie sich Folgendes an:



      Source link

      So installieren Sie Tinc und richten ein einfaches VPN unter Ubuntu 18.04 ein


      Einführung

      Tinc ist ein Open-Source Virtual Private Network (VPN)-Daemon mit nützlichen Funktionen wie Verschlüsselung, optionaler Komprimierung und automatischem Mesh-Routing, der VPN-Verkehr opportunistisch direkt zwischen Servern routen kann. Diese Funktionen unterscheiden tinc von anderen VPN-Lösungen und machen es zu einer guten Wahl für die Erstellung eines VPN aus vielen kleinen, geografisch verteilten Netzwerken.

      In diesem Tutorial gehen wir darauf ein, wie Sie mit tinc ein sicheres VPN erstellen, in dem Ihre Server kommunizieren können, als ob sie sich in einem lokalen Netzwerk befinden würden. Wir werden auch demonstrieren, wie man mit tinc einen sicheren Tunnel in einem privaten Netzwerk einrichtet. Wir verwenden Ubuntu-18.04-Server, aber die Konfigurationen können für die Verwendung mit jedem anderen Betriebssystem angepasst werden.

      Ziele

      Um mehrere Anwendungsfälle abzudecken, beschreibt dieses Tutorial die Verbindung eines Client-Knotens mit dem VPN über eine private Netzwerkschnittstelle und eines anderen über eine öffentliche. Sie können diese Einrichtung jedoch an Ihre eigenen Bedürfnisse anpassen. Sie müssen nur planen, wie Ihre Server aufeinander zugreifen sollen, und die in diesem Tutorial vorgestellten Beispiele an Ihre eigenen Bedürfnisse anpassen. Achten Sie darauf, die in den Beispielen hervorgehobenen Werte durch Ihre eigenen Werte zu ersetzen, wenn Sie eine Anpassung an Ihre eigene Einrichtung vornehmen. Es könnte jedoch in Ihrem Interesse sein, zunächst dem Tutorial in der vorliegenden Form zu folgen, um sicherzustellen, dass Sie die beteiligten Komponenten und Prozesse verstehen, bevor Sie diese Anleitung modifizieren.

      Um die Übersichtlichkeit zu wahren, wird in diesem Tutorial wie folgt auf die Server verwiesen:

      • server-01: Alle VPN-Knoten stellen eine Verbindung mit diesem Rechner her und die Verbindung muss für die einwandfreie VPN-Funktionalität aufrechterhalten werden. Weitere Server können auf die gleiche Weise wie dieser konfiguriert werden, um, falls gewünscht, Redundanz bereitzustellen.
      • client-01: Verbindet sich mit dem VPN-Knoten von server-01 über dessen private Netzwerkschnittstelle
      • client-02: Verbindet sich mit dem VPN-Knoten von server-01 über dessen öffentliche Netzwerkschnittstelle

      Anmerkung: Tinc selbst unterscheidet nicht zwischen Servern (Rechnern, die VPN-Dienste hosten und bereitstellen) und Clients (Rechnern, die sich mit dem sicheren privaten Netzwerk verbinden und es verwenden). Es kann jedoch hilfreich sein, die Funktionsweise von tinc zu verstehen und zu visualisieren, indem Sie sich Ihre Server so vorstellen.

      Hier ist eine Darstellung des VPN, das wir einrichten möchten:

      Tinc VPN-Einrichtung

      Das blaue Feld steht für unser VPN und das rosa Feld für das zugrunde liegende private Netzwerk. Alle drei Server können über das VPN kommunizieren, obwohl das private Netzwerk ansonsten für client-02 nicht zugänglich ist.

      Voraussetzungen

      Wenn Sie diesem Tutorial genau folgen möchten, stellen Sie zwei Ubuntu-18.04-Server (server-01 und client-01) in demselben Datencenter bereit und aktivieren Sie auf jedem das private Netzwerk. Erstellen Sie dann einen weiteren Ubuntu-18.04-Server (client-02) in einem separaten Datencenter. Jeder Server sollte über einen administrativen Benutzer und eine mit ufw konfigurierte Firewall verfügen. Um dies einzurichten, folgen Sie unserem Leitfaden für die Ersteinrichtung des Servers für Ubuntu 18.04.

      Zusätzlich müssen wir später in diesem Tutorial einige Dateien zwischen den einzelnen Rechnern mit scp übertragen. Aus diesem Grund müssen Sie auf jedem Ihrer Server SSH-Schlüssel generieren, die SSH-Schlüssel von client-01 und client-02 zu der Datei authorized_keys von server-01 und dann den SSH-Schlüssel von server-01 zu den Dateien authorized_keys von client-01 und client-02 hinzufügen. Hilfe für das Einrichten finden Sie in unserem Leitfaden So richten Sie SSH-Schlüssel unter Ubuntu 18.04 ein.

      Schritt 1 – Installieren von Tinc

      Tinc ist aus den Standard-APT-Repositorys von Ubuntu verfügbar, d. h., wir können es mit nur wenigen Befehlen installieren.

      Sofern Sie dies nicht kürzlich getan haben, führen Sie auf jedem Server den folgenden Befehl aus, um die jeweiligen Paketindizes zu aktualisieren:

      All servers

      Installieren Sie dann tinc auf jedem Server, indem Sie den folgenden Befehl ausführen:

      All servers

      Damit haben Sie tinc auf jedem Ihrer Server installiert. Sie müssen jedoch einige Änderungen an der Konfiguration von tinc auf jedem Rechner vornehmen, damit Ihr VPN ausgeführt werden kann. Beginnen wir mit der Aktualisierung von server-01.

      Schritt 2 – Konfigurieren des Tinc-Servers

      Tinc erfordert, dass jeder Rechner, der Teil des VPN sein wird, die folgenden drei Konfigurationskomponenten aufweist:

      • Tinc-Konfigurationsdateien: Es gibt drei verschiedene Dateien, die den tinc-Daemon konfigurieren:
        • tinc.conf, die den Netznamen, das Netzwerkgerät, über das VPN ausgeführt wird, und andere VPN-Optionen definiert;
        • tinc-up, ein Skript, das das in tinc.conf definierte Netzwerkgerät nach dem Start von tinc aktiviert;
        • tinc-down, die das Netzwerkgerät deaktiviert, wenn tinc stoppt.
      • Öffentliche/private Schlüsselpaare: Tinc verwendet öffentliche/private Schlüsselpaare, damit nur Benutzer mit gültigen Schlüsseln auf das VPN zugreifen können.
      • Host-Konfigurationsdateien: Jeder Rechner (oder Host) im VPN hat seine eigene Konfigurationsdatei, die die tatsächliche IP-Adresse des Hosts und das Subnetz, in dem tinc ihn bedient, enthält.

      Tinc verwendet einen Netznamen, um ein tinc-VPN von einem anderen zu unterscheiden. Dies ist hilfreich, wenn Sie mehrere VPNs einrichten möchten. Es wird jedoch empfohlen, einen Netznamen selbst dann zu verwenden, wenn Sie nur ein VPN konfigurieren wollen. Sie können Ihrem VPN einen beliebigen Netznamen geben, aber der Einfachheit halber nennen wir unser VPN netname.

      Erstellen Sie auf server-01 die Konfigurationsverzeichnisstruktur für das VPN:

      server-01

      • sudo mkdir -p /etc/tinc/netname/hosts

      Verwenden Sie Ihren bevorzugten Texteditor, um eine Datei tinc.conf zu erstellen. Wir verwenden hier nano:

      server-01

      • sudo nano /etc/tinc/netname/tinc.conf

      Fügen Sie der leeren Datei die folgenden Zeilen hinzu. Diese konfigurieren einen tinc-Knoten namens server_01​​​ mit einer Netzwerkschnittstelle namens tun0, die IPv4 verwenden wird:

      server-01:/etc/tinc/netname/tinc.conf

      Name = server_01
      AddressFamily = ipv4
      Interface = tun0
      

      Warnung: Beachten Sie, dass der Wert nach der Anweisung Name einen Unterstrich (_) anstatt eines Bindestrichs (-) enthält. Dies ist wichtig, da tinc verlangt, dass der Wert Name nur alphanumerische oder Unterstrich-Zeichen enthält. Wenn Sie hier einen Bindestrich verwenden, werden Sie bei dem Versuch, das VPN später in diesem Leitfaden zu starten, auf einen Fehler stoßen.

      Speichern und schließen Sie die Datei nach dem Hinzufügen dieser Zeilen. Wenn Sie nano verwendet haben, drücken Sie STRG+X, Y, dann die EINGABETASTE.

      Erstellen Sie als Nächstes eine Host-Konfigurationsdatei namens server_01 im Unterverzeichnis hosts. Letztendlich werden die Client-Knoten diese Datei zur Kommunikation mit server-01 verwenden:

      server-01

      • sudo nano /etc/tinc/netname/hosts/server_01

      Auch hier ist zu beachten, dass der Name dieser Datei einen Unterstrich und keinen Bindestrich enthält. Auf diese Weise entspricht er der Anweisung Name in der Datei tinc.conf, was es tinc ermöglicht, den öffentlichen RSA-Schlüssel des Servers automatisch an diese Datei anzuhängen, wenn wir die Datei später generieren.

      Fügen Sie die folgenden Zeilen in die Datei ein und stellen Sie sicher, dass die öffentliche IP-Adresse von server-01 enthalten ist:

      server-01:/etc/tinc/netname/hosts/server_01

      Address = server-01_public_IP_address
      Subnet = 10.0.0.1/32
      

      Das Feld Address gibt an, wie sich andere Knoten mit diesem Server verbinden werden, und Subnet gibt an, welches Subnetz dieser Daemon bedienen wird. Speichern und schließen Sie die Datei.

      Erzeugen Sie als Nächstes mit dem folgenden Befehl ein Paar öffentlicher und privater RSA-Schlüssel für diesen Host:

      server-01

      • sudo tincd -n netname -K4096

      Nach Ausführung dieses Befehls werden Sie dazu aufgefordert, die Dateinamen einzugeben, unter denen tinc die öffentlichen und privaten RSA-Schlüssel speichern wird:

      Output

      . . . Please enter a file to save private RSA key to [/etc/tinc/netname/rsa_key.priv]: Please enter a file to save public RSA key to [/etc/tinc/netname/hosts/server_01]:

      Drücken Sie die EINGABETASTE, um bei jeder Eingabeaufforderung die Standardspeicherorte zu akzeptieren; dadurch wird tinc angewiesen, den privaten Schlüssel in einer Datei namens rsa_key.priv zu speichern und den öffentlichen Schlüssel an die Host-Konfigurationsdatei server_01 anzuhängen.

      Als Nächstes erstellen Sie tinc-up, das Skript, das bei jedem Start des VPNs netname ausgeführt wird:

      server-01

      • sudo nano /etc/tinc/netname/tinc-up

      Fügen Sie die folgenden Zeilen hinzu:

      server-01:/etc/tinc/netname/tinc-up

      #!/bin/sh
      ip link set $INTERFACE up
      ip addr add 10.0.0.1/32 dev $INTERFACE
      ip route add 10.0.0.0/24 dev $INTERFACE
      

      Im Folgenden wird erläutert, was jede dieser Zeilen bewirkt:

      • ip link …: setzt den Status der virtuellen Netzwerkschnittstelle von tinc auf up
      • ip addr …: fügt der virtuellen Netzwerkschnittstelle von tinc die IP-Adresse 10.0.0.1 mit einer Netzmaske von 32 hinzu, wodurch die anderen Rechner im VPN die IP-Adressse von server-01 als 10.0.0.1 sehen.
      • ip route …: fügt eine Route (10.0.0.0/24) hinzu, die über die virtuelle Netzwerkschnittstelle von tinc erreicht werden kann.

      Speichern und schließen Sie die Datei nach dem Hinzufügen dieser Zeilen.

      Als Nächstes erstellen Sie ein Skript, um die virtuelle Netzwerkschnittstelle zu entfernen, wenn Ihr VPN gestoppt wird:

      server-01

      • sudo nano /etc/tinc/netname/tinc-down

      Fügen Sie die folgenden Zeilen hinzu:

      server-01:/etc/tinc/netname/tinc-down

      #!/bin/sh
      ip route del 10.0.0.0/24 dev $INTERFACE
      ip addr del 10.0.0.1/32 dev $INTERFACE
      ip link set $INTERFACE down
      

      Diese Zeilen haben die entgegengesetzte Wirkung wie die des Skripts tinc-up:

      • ip route …: Löscht die Route 10.0.0.0/24
      • ip addr …: löscht die IP-Adresse 10.0.0.1 aus der virtuellen Netzwerkschnittstelle von tinc
      • ip link …: setzt den Status der virtuellen Netzwerkschnittstelle von tinc auf down

      Speichern und schließen Sie die Datei und machen Sie dann diese beiden neuen Netzwerkskripte ausführbar:

      server-01

      • sudo chmod 755 /etc/tinc/netname/tinc-*

      Fügen Sie als letzten Schritt der Konfiguration von server-01 eine Firewall-Regel hinzu, die den Datenverkehr über Port 655, den Standardport von tinc, zulässt:

      server-01

      server-01 ist nun vollständig konfiguriert und Sie können mit der Einrichtung Ihrer Client-Knoten fortfahren.

      Schritt 3 – Konfigurieren der Client-Knoten

      Ihre beiden Client-Rechner benötigen eine etwas andere Konfiguration als der Server, obwohl der Prozess im Allgemeinen recht ähnlich ist.

      Aufgrund der Einrichtung, die wir in diesem Leitfaden anstreben, werden wir client-01 und client-02 fast identisch, mit nur wenigen kleinen Unterschieden zwischen ihnen, konfigurieren. Daher müssen viele der in diesem Schritt gegebenen Befehle auf beiden Rechnern ausgeführt werden. Beachten Sie jedoch, dass, wenn client-01 oder client-02 einen bestimmten Befehl oder eine spezielle Konfiguration erfordern, diese Anweisungen in einem blauen bzw. roten Befehlsblock angezeigt werden.

      Replizieren Sie sowohl auf client-01 als auch auf client-02 die auf server-01 erstellte Verzeichnisstruktur:

      client-01 & client-02

      • sudo mkdir -p /etc/tinc/netname/hosts

      Erstellen Sie dann eine Datei tinc.conf:

      client-01 & client-02

      • sudo nano /etc/tinc/netname/tinc.conf

      Fügen Sie auf beiden Rechnern die folgenden Zeilen in die Datei ein:

      client-01 & client-02 /etc/tinc/netname/tinc.conf

      Name = node_name
      AddressFamily = ipv4
      Interface = tun0
      ConnectTo = server_01
      

      Stellen Sie sicher, dass Sie node_name durch den Namen des jeweiligen Client-Knotens ersetzen. Auch hier ist darauf zu achten, dass dieser Name einen Unterstrich (_) und keinen Bindestrich verwendet.

      Beachten Sie, dass diese Datei eine Anweisung ConnectTo enthält, die auf server_01 verweist, während die Datei tinc.conf von server-01 diese Anweisung nicht enthält. Indem Sie keine Anweisung ConnectTo auf server-01 einbinden, bedeutet dies, dass server-01 nur auf eingehende Verbindungen lauscht. Dies funktioniert für unsere Einrichtung, da es keine Verbindung mit anderen Rechnern herstellt.

      Speichern und schließen Sie die Datei.

      Erstellen Sie als Nächstes auf jedem Client-Knoten eine Host-Konfigurationsdatei. Achten Sie auch hier darauf, dass der Dateinamen mit einem Unterstrich statt eines Bindestrichs geschrieben wird:

      client-01 & client-02

      • sudo nano /etc/tinc/netname/hosts/node_name

      Fügen Sie für client-01 diese Zeile hinzu:

      client-01:/etc/tinc/netname/hosts/client_01

      Subnet = 10.0.0.2/32
      

      Fügen Sie für client-02 diese Zeile hinzu:

      client-02:/etc/tinc/netname/hosts/client_02

      Subnet = 10.0.0.3/32
      

      Beachten Sie, dass jeder Client ein anderes Subnetz hat, das tinc bedienen wird. Speichern und schließen Sie die Datei.

      Erzeugen Sie als Nächstes auf jedem Client-Rechner die Schlüsselpaare:

      client-01 & client-02

      • sudo tincd -n netname -K4096

      Wie auch bei server-01 drücken Sie bei der Aufforderung zur Dateiauswahl zum Speichern der RSA-Schlüssel die EINGABETASTE, um die Standardauswahl zu akzeptieren.

      Erstellen Sie anschließend das Startskript für die Netzwerkschnittstelle auf jedem Client:

      client-01 & client-02

      • sudo nano /etc/tinc/netname/tinc-up

      Fügen Sie für client-01 diese Zeilen hinzu:

      client-01:/etc/tinc/netname/tinc-up

      #!/bin/sh
      ip link set $INTERFACE up
      ip addr add 10.0.0.2/32 dev $INTERFACE
      ip route add 10.0.0.0/24 dev $INTERFACE
      

      Fügen Sie für client-02 Folgendes hinzu:

      client-02:/etc/tinc/netname/tinc-up

      #!/bin/sh
      ip link set $INTERFACE up
      ip addr add 10.0.0.3/32 dev $INTERFACE
      ip route add 10.0.0.0/24 dev $INTERFACE
      

      Speichern und schließen Sie jede Datei.

      Erstellen Sie als Nächstes auf jedem Client das Stoppskript für die Netzwerkschnittstelle:

      client-01 & client-02

      • sudo nano /etc/tinc/netname/tinc-down

      Fügen Sie auf client-01 den folgenden Inhalt in die leere Datei ein:

      client-01:/etc/tinc/netname/tinc-down

      #!/bin/sh
      ip route del 10.0.0.0/24 dev $INTERFACE
      ip addr del 10.0.0.2/32 dev $INTERFACE
      ip link set $INTERFACE down
      

      Fügen Sie auf client-02 Folgendes hinzu:

      client-02:/etc/tinc/netname/tinc-down

      #!/bin/sh
      ip route del 10.0.0.0/24 dev $INTERFACE
      ip addr del 10.0.0.3/32 dev $INTERFACE
      ip link set $INTERFACE down
      

      Speichern und schließen Sie die Dateien.

      Machen Sie Netzwerkskripte ausführbar, indem Sie auf jedem Client-Rechner den folgenden Befehl ausführen:

      client-01 & client-02

      • sudo chmod 755 /etc/tinc/netname/tinc-*

      Öffnen Sie zum Schluss Port 655 auf jedem Client:

      client-01 & client-02

      Zu diesem Zeitpunkt sind die Client-Knoten fast, wenn auch nicht ganz, eingerichtet. Sie benötigen noch den öffentlichen Schlüssel, den wir im vorherigen Schritt auf server-01 erstellt haben, um die Verbindung zum VPN zu authentifizieren.

      Schritt 4 – Verteilen der Schlüssel

      Jeder Knoten, der direkt mit einem anderen Knoten kommunizieren möchte, muss öffentliche Schlüssel ausgetauscht haben, die sich innerhalb der Host-Konfigurationsdateien befinden. In unserem Fall muss server-01 öffentliche Schlüssel mit den anderen Knoten austauschen.

      Austauschen von Schlüsseln zwischen server-01 und client-01

      Kopieren Sie auf client-01 seine Host-Konfigurationsdatei auf server-01. Da sich sowohl client-01 als auch server-01 im selben Datencenter befinden und beide über ein aktiviertes privates Netzwerk verfügen, können Sie hier die private IP-Adresse von server01 verwenden:

      client-01

      • scp /etc/tinc/netname/hosts/client_01 sammy@server-01_private_IP:/tmp

      Kopieren Sie dann auf server-01 die Host-Konfigurationsdatei von client-01 in das Verzeichnis /etc/tinc/netname/hosts/:

      server-01

      • sudo cp /tmp/client_01 /etc/tinc/netname/hosts/

      Kopieren Sie dann, während Sie sich noch auf server-01 befinden, dessen Host-Konfigurationsdatei nach client-01:

      server-01

      • scp /etc/tinc/netname/hosts/server_01 user@client-01_private_IP:/tmp

      Kopieren Sie auf client-01 die Datei von server-01 an den entsprechenden Ort:

      client-01

      • sudo cp /tmp/server_01 /etc/tinc/netname/hosts/

      Bearbeiten Sie auf client-01 die Host-Konfigurationsdatei von server-01, damit das Feld Address auf die private IP-Adresse von server-01 gesetzt wird. Auf diese Weise verbindet sich client-01 über das private Netzwerk mit dem VPN:

      client-01

      • sudo nano /etc/tinc/netname/hosts/server_01

      Ändern Sie die Anweisung Address so, dass sie auf die private IP-Adresse von server-01 verweist:

      client-01:/etc/tinc/netname/hosts/server_01

      Address = server-01_private_IP
      Subnet = 10.0.0.1/32
      

      Speichern und beenden. Gehen wir jetzt zu unserem verbleibenden Knoten, client-02.

      Austauschen von Schlüsseln zwischen server-01 und client-02

      Kopieren Sie auf client-02 seine Host-Konfigurationsdatei auf server-01:

      client-02

      • scp /etc/tinc/netname/hosts/client_02 sammy@server-01_public_IP:/tmp

      Kopieren Sie dann auf server-01 die Host-Konfigurationsdatei von client_02 an den entsprechenden Ort:

      server-01

      • sudo cp /tmp/client_02 /etc/tinc/netname/hosts/

      Kopieren Sie dann die Host-Konfigurationsdatei von server-01 nach client-02:

      server-01

      • scp /etc/tinc/netname/hosts/server_01 user@client-02_public_IP:/tmp

      Kopieren Sie auf client-02 die Datei von server-01 an den entsprechenden Ort:

      client-02

      • sudo cp /tmp/server_01 /etc/tinc/netname/hosts/

      Angenommen, Sie richten nur zwei Client-Knoten ein, dann sind Sie mit der Verteilung der öffentlichen Schlüssel fertig. Wenn Sie jedoch ein größeres VPN erstellen, ist jetzt ein guter Zeitpunkt, um die Schlüssel zwischen diesen anderen Knoten auszutauschen. Denken Sie daran, dass, wenn zwei Knoten direkt miteinander kommunizieren sollen (ohne einen Weiterleitungsserver dazwischen), sie ihre Schlüssel/Host-Konfigurationsdateien ausgetauscht haben müssen und auf die realen Netzwerkschnittstellen des jeweils anderen zugreifen können müssen. Es ist außerdem in Ordnung, die Konfigurationsdatei jedes Hosts einfach auf jeden Knoten im VPN zu kopieren.

      Schritt 5 – Testen der Konfiguration

      Starten Sie tinc auf jedem Knoten, beginnend mit server-01, mit dem folgenden Befehl:

      All servers

      • sudo tincd -n netname -D -d3

      Dieser Befehl enthält das Flag -n, das auf den Netznamen für unser VPN netname verweist. Dies ist nützlich, wenn Sie mehr als ein VPN eingerichtet haben und Sie angeben müssen, welches Sie starten möchten. Es enthält auch das Flag -D, das die Gabelung und Trennung von tinc verhindert sowie den automatischen Neustartmechanismus von tinc deaktiviert. Schließlich enthält es das Flag -d, das tinc anweist, im Debug-Modus mit einem Debug-Level von 3 zu laufen.

      Anmerkung: Wenn es um den tinc-Daemon geht, zeigt ein Debug-Level von 3 jede zwischen zwei beliebigen Servern ausgetauschte Anfrage, einschließlich Authentifizierungsanforderungen, Schlüsselaustausch und Aktualisierungen von Verbindungslisten. Höhere Debug-Level zeigen mehr Informationen über den Netzwerkverkehr an, aber im Moment geht es nur darum, ob die Knoten miteinander kommunizieren können. Daher ist ein Level von 3 ausreichend. In einem Produktivszenario würden Sie jedoch zu einem niedrigeren Debug-Level wechseln wollen, um die Festplatten nicht mit Protokolldateien zu füllen.

      Sie können mehr über die Debug-Level von tinc erfahren, indem Sie die offizielle Dokumentation durchsehen.

      Nachdem der Daemon auf jedem Knoten gestartet wurde, sollten Sie eine Ausgabe mit den Namen der einzelnen Knoten sehen, wenn sie sich mit server-01 verbinden. Testen wir jetzt die Verbindung über das VPN.

      Pingen Sie in einem separaten Fenster auf client-02 die VPN IP-Adresse von client-01 an. Dieser haben wir zuvor 10.0.0.2 zugewiesen:

      client-02

      Der Ping sollte korrekt funktionieren und Sie sollten in den anderen Fenstern einige Debug-Ausgaben über die Verbindung im VPN sehen. Dies zeigt an, dass client-02 über das VPN über server-01 mit client-01 kommunizieren kann. Drücken Sie STRG+C, um den Ping zu beenden.

      Sie können die VPN-Schnittstellen auch für jede andere Netzwerkkommunikation verwenden, z. B. für Anwendungsverbindungen, das Kopieren von Dateien und SSH.

      Beenden Sie in jedem Debug-Fenster des tinc-Daemons den Daemon durch drücken von STRG+.

      Schritt 6 – Konfigurieren von Tinc zum Starten beim Booten

      Ubuntu-Server verwenden systemd als standardmäßigen Systemmangager, um das Starten und Ausführen von Prozessen zu steuern. Aus diesem Grund können wir das VPN netname so aktivieren, dass es beim Booten mit einem einzigen systemctl-Befehl automatisch gestartet wird.

      Führen Sie den folgenden Befehl auf jedem Knoten aus, um das tinc-VPN so einzustellen, dass es bei jedem Booten des Rechners startet:

      All servers

      • sudo systemctl enable tinc@netname

      Tinc ist so konfiguriert, dass es auf jedem Rechner beim Booten startet, und Sie können es mit dem Befehl systemctl steuern. Wenn Sie es jetzt starten möchten, führen Sie den folgenden Befehl auf jedem Ihrer Knoten aus:

      All servers

      • sudo systemctl start tinc@netname

      Anmerkung: Wenn Sie mehrere VPN haben, aktivieren oder starten jedes sofort, so wie hier:

      All servers

      • sudo systemctl start tinc@natename_01 tinc@netname_02 … tinc@netname_n

      Damit ist Ihr tinc-VPN vollständig konfiguriert und wird auf jedem Ihrer Knoten ausgeführt.

      Zusammenfassung

      Nachdem Sie dieses Tutorial durchgearbeitet haben, sollten Sie nun eine gute Grundlage haben, um Ihr VPN entsprechend Ihren Bedürfnissen auszubauen. Tinc ist sehr flexibel und jeder Knoten kann so konfiguriert werden, dass er sich mit jedem anderen Knoten (auf den er über das Netzwerk zugreifen kann) verbindet, sodass er als Mesh-VPN fungieren kann, ohne sich auf einen einzelnen Knoten zu verlassen.



      Source link