Kategorien
Angular

Angular Unit Testing von Komponenten mit Abhängigkeiten

Um einen Unit Test von einer Angular Komponente mit Abhängigkeiten (Dependencies) schreiben zu können, muss man die Abhängigkeiten unter Kontrolle bringen.

Wenn man dies nicht tut, schreibt man einen Integration-Test. Integration-Tests haben eine höhere Komplexität hat und gehen schneller kaputt.

Beipsiel einer Komponente „MyComponent“ mit einer Abhängikeit „MyService“:

export class MyComponent implements OnInit {

  public serviceValue;

  constructor(
    private myService: MyService,
  ) {}

  ngOnInit() {
    this.serviceValue =  this.myService.getValue();
  }
}

Um in einem Jasmine Unit Test die Abhängigkeit von MyService zu kontrollieren, kann man einen Spy einsetzen, der an die die Stelle der Methode MyService::getValue gesetzt wird und die Sachen macht, die man für den Test gern hätte.

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;
  let myService:MyService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [MyComponent,],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    myService = TestBed.inject(MyService);
  });

  it('spy should set value', () => {
     spyOn(myService,'getValue').and.returnValue('foo');
    fixture.detectChanges();
    expect(component.serviceValue).toBe('foo');
  });
});

In dem Beispiel wird die Methode getValue der Klasse MyService mit einem Spy überschrieben und dem Spy mitgeteilt, dass er den Wert ‚foo‘ zurückgeben soll.

Kategorien
MQTT

MQTT lesen aller Topics

Um alle Topics eines MQTT Brokers zu abonnieren und zu lesen reicht der Befehl aus (bei localhost, Port 1883):

mosquitto_sub -v -t '#'

oder bei nicht localhost und nicht standard Port:

mosquitto_sub -v -h BROKER_IP -p BROKER_PORT -t '#'

Kategorien
Amazon AWS

AWS API Gateway für DynamoDB erstellen

Die Aufgabe ist es eine REST API zu erstellen mit AWS API Gateway, welche Daten lesen und später auch schreiben kann von einer Dynamo DB ohne eine Lambda Funktion zu verwenden.

Wir werden das API Gateway so konfigurieren, dass es die Request Daten validiert und so verändert, dass der DynamoDB Service die Daten verarbeitet.

Dazu erstellen wir in AWS ein nicht private REST API:

REST API Gateway erstellen
DynamoDB NoSql Datenbank erstellen

Berechtigungen vergeben, dass das API Gateway auf die DynamoDB Tabelle zugreifen kann in AWS IAM. Dafür erstellen wir eine Richtlinie in IAM. Die Berechtigung sieht wie folgt aus:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "dynamodb:Scan"
      ],
      "Effect": "Allow",
      "Resource": "ENTER_ARN_OF_YOUR_DYNAMO_DB_HERE"
    }
  ]
}

Die Richtlinie kann mit dem Tool genriert werden: https://awspolicygen.s3.amazonaws.com/policygen.html

Dann muss eine Rolle mit der Richtlinie für den Service API Gateway erstellt werden in IAM. Hinweis: Die Richtlinie kann erst nach dem erstellen der Rolle hinzugefügt werden.

IAM Rolle für das API Gateway mit der erstellten Richtlinie

Dann Erstellen wir in AWS API Gateway eine Resource mit der Richtlinie:

Jetzt wandeln wir den einfacher GET Request /data/all mit leerem Payload um zu einem DynamoDB POST Request, der alle Daten zurückliefert, beschrieben in: https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html

Die Integrationsanforderung der Resource muss folger Maßen konfiguriert sein:

Weiterleiten des Requests an Dynamo DB als POST Scan Request

Der Request kann über den Testen Button in AWS ausprobiert werden:

Erfolgreicher Test des Endpunktes
Kategorien
Amazon AWS

AWS Policy Generator

Zum generieren von Berechtigungen gibt eine einfaches Web-Tool von AWS, welches einem die Berechtigungen im JSON Format generiert.

Type of policy ist meist IAM policy.

https://awspolicygen.s3.amazonaws.com/policygen.html

Kategorien
Amazon AWS

APIs erstellen mit AWS API Gateway

Amazon API Gateway ist eine Service mit dem man professionelle APIs erstellen kann, welche

  • skalieren
  • sich um die Authentifizierung kümmern
  • serverless sein können zu AWS Labmda oder einem AWS Datern Speicher
  • aber auch über HTTP(S) zu einer EC2 Instanz weiterleiten können
mögliche verbindbare serverless Services
serverless Datenbank Funktionalität
serverless Website Hosting

Das AWS API Gateway kann den Request validieren, verändern und auch beantworten/abbrechen um Traffic zu sparen.

Zur Authentifizierung können verschiedene Methoden verwendet werden über JWT Token, Basic Auth, Custom HTTP URLs und eigene Lambda Funktion.

https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-control-access-to-api.html

AWS Cognito zur Authentifizierung und User Management

Man kann aus der Schnittstelle ein SDK generieren, welches die Client zur Integration verwenden können und die Authentifizierung erleichtern. Derzeit werden unterstützt:

  • Java
  • Android
  • iOS
  • JavaScript
  • Ruby

https://docs.aws.amazon.com/de_de/apigateway/latest/developerguide/how-to-generate-sdk-console.html

Erstellen eines SDK für Clients zum einbinden der Schnittstelle

Alles Requests/Responses können gelogt werden und analysiert werden in AWS CloudWatch.

Die CORS Regeln können damit verwaltet werden.

Die API lässt sich mit Hilfe von Open API/Swagger 3.0 definieren.

Es können neben REST/HTTP auch Websocket APIs erstellt werden.

Canary Release Möglichkeit: Es werden nur 10% der Requests auf den neu releasten Endpunkt, um zu schauen, ob soweit alles funktioniert:

https://docs.aws.amazon.com/apigateway/latest/developerguide/canary-release.html

Preis-Übersicht:

https://aws.amazon.com/de/api-gateway/pricing/

Empfohlene Videos:

Kategorien
JavaScript

Funktionale Programmierung mit JavaScript

JavaScript ist keine funktionale Programmiersprache, aber die wichtigsten Prinzipien der funktionalen Programmierung lassen sich trotzdem beim Programmieren an einigen Stellen anwenden.

Diese Prinzipien sind u.a.:

  1. Funktionen haben Inputs und Outputs, aber keine Nebenwirkungen. Sie bearbeiten keine Daten, die ihnen nicht explizit übergeben wurden
  2. Variablen werden nicht verändert (Immortabilität)
  3. Funktionen höherer Ordnung dienen zur Schachtelung von Funktionen. Als Input (Parameter) von Funktionen werden wiederum Funktionen verwendet

An den folgende Beispielen und sehr praktischen Array Methoden soll der Unterschied zwischen funktionaler und nicht funktionaler Programmierung gezeigt werden:

forEach

Die Array methode forEach ermöglicht es funktional über ein Array zu iterieren, ohne eine sich verändernde Variable i zu verwenden.

// Beispiel alle Einträge in companies auf der Konsole ausgeben

//nicht funktional
for(let i = 0; i < companies.length; i++) {
   console.log(companies[i]);
}

//funktional
companies.forEach(function(company) {
  console.log(company);
});

filter

Die Array Methode filter entfernt Einträge aus einem Array, die nicht der übergebenen Bedingung entsprechen und gibt diese zurück. Man beachte, dass das ursprüngliche Array nicht verändert wird und die filter Mezhode selbst ein Funktion höherer Ordnung ist, weil als Parameter eine Funktion übergeben wird.

Kategorien
GIT

GIT mit SSH Verbindung unter Windows mit Ubuntu WSL2 einrichten

Um eine sichere Kommunikation mit einem GIT Repository über SSH einzurichten, muss man unter Windows 10 folgender Maßen vorgehen.

  1. Einen SSH Schlüssel generieren (private und publid), dieser wird automatisch in das Vezeichnis ~/.ssh (Home Verzeichnis des Users) gelegt.
ssh-keygen -o

2. Der public key muss dann hinterlegt werden beim GIT repository. Dies passiert meist über eine Web GUI. Der private Key verlässt nie die Person, die den Key generiert hat, ansonsten ist die Sicherheit nicht gegeben.

Der private Key muss mindestens die Dateirechte 600 haben, damit nur der Inhaber Darauf Zugriff hat, anosnten verweigert der ssh-agent die Zusammenarbeit und weißt auf die falschen Zugriffsrechte zu.

3. Eintragen der Verbindungsdaten in die ssh config Datei ~/.ssh/config

Host github.com
IdentityFile ~/.ssh/.ssh/my_host_public_key

Bei falschen Zugriffsrechten des private Keys erscheint die Meldung:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/home/user/.ssh/my_key' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.

Die Zugriffsrechte 644 sind zu freigiebig. Hier sind angemessener 600 Rechte:

chmod 600 /home/user/.ssh/my_key

4. privaten SSH Key dem SSH Agent mitteilen:

ssh-add ~/.ssh/my_host_private_key

5. Test der SSH Verbindung (optional). Hinweis: Hier muss auch die Subdomain hinter dem @ verwendet werden. Es erscheint dann eine Willkommensnachricht bei Erfolg.

ssh -T git@github.com

6. GIT clone mit SSH

git clone git@github.com:sample-username/sample-repo.git
Kategorien
Docker

Docker Windows Port freigeben

Wenn man in Docker unter Windows die Fehlermeldung bekommt:

Error response from daemon: Ports are not available: listen tcp 0.0.0.0:12345: bind: An attempt was made to access a socket in a way forbidden by its access permissions.

Dann muss der Port 12345 freigeben werden:

Dazu muss man auf der Powsershell Konsole mit Admin Rechten erstmal checken, welche Ports schon freiegeben wurden:

netsh interface ipv4 show excludedportrange protocol=tcp

Wenn der Port in den Port Ranges nicht vorhanden ist, kann er folgender Maßen hinzugefügt werden:
  1. Hyper-V ausschalten (und PC neustarten danach)

dism.exe /Online /Disable-Feature:Microsoft-Hyper-V

2.Port hinzufügen:

netsh int ipv4 add excludedportrange protocol=tcp startport=12345 numberofports=1

3. Hyper-V wieder aktivieren (und PC neustarten danach)

dism.exe /Online /Enable-Feature:Microsoft-Hyper-V /All

Kategorien
PHP

PHP-FFMpeg resize Video

Um mit PHP-FFMpeg ein Video auf eine maximale Größe und Breite zu resizen kann man folgenden Code verwenden:

<?php

namespace App\Core\Media;

use FFMpeg\Coordinate\Dimension;
use FFMpeg\FFMpeg;
use FFMpeg\Filters\Video\ResizeFilter;
use FFMpeg\Format\Video\X264;
use FFMpeg\Media\Video;

class VideoManager
{

    /**
     * @var FFMpeg
     */
    private $ffmpeg;

    /**
     * @var int
     */
    private $videoMaxHeight;

    /**
     * @var int
     */
    private $videoMaxWidth;

    /**
     * @var Video|null
     */
    private $video;

    /**
     * @var string|null
     */
    private $videoPath;

    /**
     * @param FFMpeg $ffmpeg
     * @param int $videoMaxHeight
     * @param int $videoMaxWidth
     */
    public function __construct(
        FFMpeg $ffmpeg,
        int $videoMaxHeight,
        int $videoMaxWidth
    )
    {
        $this->ffmpeg = $ffmpeg;
        $this->videoMaxHeight = $videoMaxHeight;
        $this->videoMaxWidth = $videoMaxWidth;
    }

    /**
     * @param string|null $videoPath
     * @return VideoManager
     */
    public function setVideoPath(?string $videoPath): VideoManager
    {
        $this->videoPath = $videoPath;
        if (!file_exists($videoPath)) {
            throw new \Exception('video does not exists:' . $videoPath);
        }
        $this->video = $this->ffmpeg->open($videoPath);

        return $this;
    }



    /**
     * @return int
     */
    public function getHeight(): int
    {
        return $this->video->getStreams()->first()->getDimensions()->getHeight();
    }

    /**
     * @return int
     */
    public function getWidth(): int
    {
        return $this->video->getStreams()->first()->getDimensions()->getWidth();
    }

    /**
     * @return void
     */
    public function formatH264Codec()
    {
        $format = new X264();

        $tmpDirectory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'video_format' . DIRECTORY_SEPARATOR;
        if (!is_dir($tmpDirectory)) {
            mkdir($tmpDirectory);
        }

        $tmpFilePath = $tmpDirectory . uniqid(time()) . '.mp4';

        $this->resizeVideo();

        $format
            ->setVideoCodec('libx264')
         ;

        $this->video->save($format, $tmpFilePath);

        // replace with encoded file
        unlink($this->videoPath);
        copy($tmpFilePath, $this->videoPath);

        //reset ffmpeg
        $this->ffmpeg = FFMpeg::create();
        $this->video = $this->ffmpeg->open($this->videoPath);
    }

    /**
     * @return void
     */
    protected function resizeVideo()
    {
        $width  = $this->getWidth();
        $height = $this->getHeight();

        if ($width > $this->videoMaxWidth || $height > $this->videoMaxHeight) {
            $factorWidth = $width / $this->videoMaxWidth;
            $factorHeight = $height / $this->videoMaxHeight;

            if ($factorWidth > $factorHeight) {
                $width = $width / $factorWidth;
                $height = $height / $factorWidth;
                $mode = ResizeFilter::RESIZEMODE_SCALE_WIDTH;
            } else {
                $width = $width / $factorHeight;
                $height = $height / $factorHeight;
                $mode = ResizeFilter::RESIZEMODE_SCALE_HEIGHT;
            }

            //round to the nearest odd number: ffmpeg otherwise throws an error
            $height = $this->roundToNextOddNumber($height);
            $width  = $this->roundToNextOddNumber($width);

            $this->video->filters()->resize(
                new Dimension($width, $height),
                $mode
            );
        }
    }

    /**
     * @param float $number
     * @return int
     */
    private function roundToNextOddNumber(float $number): int
    {
        $number = ceil($number); // Round up decimals to an integer
        if($number % 2 == 1) $number++;

        return $number;
    }
}

Usage:

$videoManager = new VideoManager(FFMpeg\FFMpeg::create(), 200, 300);
$videoManager->setVideoPath('/path/to/video.dat');
$this->videoManager->formatH264Codec();
Kategorien
PHP Symfony Testing

Symfony System-Test für valide Konfiguration der Umgebungen

Es gibt in Symfony die Möglichkeit mehrere Umgebungen (Environments) anzulegen und verschiedenartig zu konfigurieren. Das Problem ist, dass die automatisierten Tests immer gegen die Test-Umgebung und somit auch gegen die Test-Konfiguration gemacht werden.

Wie kann man sicher gehen, dass die produktive Konfiguration valide ist und dies mit einem automatisierten Test absichern?

Die Antwort ist sehr einfach, sollte eine Umgebungs-Konfiguration syntaktisch falsch sein, schlägt der Test fehl:

<?php

namespace App\Tests\System;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BootKernelTest extends WebTestCase
{

    /**
     * @return array
     */
    public function dataProviderTestBootKernel(): array
    {
        return [
            'dev' => ['dev'],
            'test' => ['test'],
            'prod' => ['prod'],
        ];
    }

    /**
     * the test would fail if the environment config is wrong
     *
     * @dataProvider dataProviderTestBootKernel
     *
     * @param string $environment
     * @return void
     */
    public function testBootKernel(string $environment)
    {
        $kernel = $this->bootKernel(['environment' => $environment]);

        $this->assertNotEmpty($kernel);
    }

}