wipflash's avatar

Create a unit test for construct

I am using Mockery for the mocking and I am unable to test these three methods. I am using phpseclib3 as a third-party package and I am always getting phpseclib3\Exception\NoKeyLoadedException : Unable to read key. I am trying to mock this class but always fail.

<?php

namespace App\Services;

use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Net\SFTP;

class SftpService
{
    public SFTP $sftp;

    protected string $customer;

    protected string $adminUsername;

    public function __construct(SFTP $sftp)
    {
        $this->sftp = $sftp;

        $this->customer = 'customer-name';

        $this->adminUsername = config('sftp.username');

        $this->sftpLogin();
    }

    protected function sftpLogin(): void
    {
        if (!$this->sftp->login(config('sftp.username'), $this->loadPrivateKey())) {
            throw new \Exception('SFTP login failed due to invalid credentials.');
        }
    }

    protected function loadPrivateKey(): AsymmetricKey
    {
        return PublicKeyLoader::load(base64_decode(config('sftp.private_key')));
    }

    public function createSftpUser(): bool
    {
        $created = false;

        if (!$this->userExist()) {
            $this->sftp->exec('sudo useradd -m ' . $this->customer);
            $created = true;
        }

        return $created;
    }

    public function sftpServiceStatus(): bool
    {
        if ($this->sftp->ping()) {
            return true;
        }
        return false;
    }

    protected function directoryExist(string $path): bool
    {
        if ($this->sftp->exec('sudo test -d "/home/' . $path . '" && echo "true" || echo "false"') == 'true') {
            return true;
        }
        return false;
    }

    protected function userExist(): bool
    {
        if ($this->sftp->exec("getent passwd | grep -c '^" . $this->customer . ":'") == 1) {
            return true;
        }
        return false;
    }

    public function setPublicSSHKey(string $key): bool
    {
        try {
            $this->createSSHDirectoryWithAdminPermissions();

            $this->sftp->exec('echo ' . $key . ' > /home/' . $this->customer . '/.ssh/authorized_keys');

            $this->setUserPermissions();

            return true;
        } catch (\Exception $exception) {
            return false;
        } finally {
            $this->sftp->disconnect();
        }
    }

    protected function createSSHDirectoryWithAdminPermissions(): void
    {
        $this->sftp->exec('sudo chgrp ' . $this->adminUsername . ' /home/' . $this->customer);

        if (!$this->directoryExist($this->customer . '/.ssh')) {
            $this->sftp->exec('sudo mkdir -p /home/' . $this->customer . '/.ssh');
        }

        $this->sftp->exec('sudo chown ' . $this->adminUsername . ' /home/' . $this->customer . '/.ssh');
    }

    public function removePublicSSHKey(): bool
    {
        try {
            $this->createSSHDirectoryWithAdminPermissions();

            $this->sftp->exec('rm /home/' . $this->customer . '/.ssh/authorized_keys');

            $this->setUserPermissions();

            return true;
        } catch (\Exception $exception) {
            return false;
        } finally {
            $this->sftp->disconnect();
        }
    }

    protected function setUserPermissions(): void
    {
        $this->sftp->exec('sudo chgrp -R ' . $this->customer . ' /home/' . $this->customer);
        $this->sftp->exec('sudo chown -R ' . $this->customer . ' /home/' . $this->customer );
    }

    public function getFileDetails(): array
    {
        $this->createSSHDirectoryWithAdminPermissions();

        if (!$this->sftp->file_exists('/home/' . $this->customer . '/.ssh/authorized_keys')) {
            return [];
        }

        $fileDetails = [
            'fingerprint' => $this->sftp->exec('sudo ssh-keygen -E md5 -lf "/home/spend-cloud/.ssh/authorized_keys" | awk \'{ print  }\' | cut -c 5-'),
            'created_date' => $this->sftp->exec('stat --printf="%z" /home/' . $this->customer . '/.ssh/authorized_keys'),
        ];

        $this->setUserPermissions();

        return $fileDetails;
    }
}

0 likes
8 replies
wipflash's avatar

@Sinnbeck I tried this for that trow an exception if not valid

public function testConstruct(){

        $sftp = \Mockery::mock(SFTP::class)->makePartial();
        $key = '-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp
wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5
1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh
3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2
pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX
GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il
AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF
L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k
X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl
U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=
-----END RSA PRIVATE KEY-----';
        \Config::set('sftp.private_key', base64_encode($key));
        \Config::set('sftp.username', 'sftp-user');

        $this->service->shouldReceive('loadPrivateKey')->withNoArgs()->andReturn('key');

        $this->service->shouldReceive('sftpLogin')->andThrow(\Exception::class);
}
Sinnbeck's avatar

@wipflash what is $this->service? I assume it's a mock set somewhere else?

And are you just trying to mock all classes? So no actual classes are getting called?

wipflash's avatar

@Sinnbeck I am sorry I forgot to add this part.

protected SftpService $service;

    protected function setUp(): void
    {
        $this->service = \Mockery::mock(SftpService::class)
            ->makePartial()
            ->shouldAllowMockingProtectedMethods();

        parent::setUp();
    }
Sinnbeck's avatar

@wipflash Ok so what are you planning on testing if you just mock everything ? I only see mocks, no actual tests. In this case I would assume that the service is real..

wipflash's avatar

@Sinnbeck I am completely new to this and have no idea where or what to start. Here is a complete class on what I want to write tests.

<?php

namespace App\Services;

use phpseclib3\Crypt\Common\AsymmetricKey;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Net\SFTP;

class SftpService
{
    public SFTP $sftp;

    protected string $customer;

    protected string $adminUsername;

    public function __construct(SFTP $sftp)
    {
        $this->sftp = $sftp;

        $this->customer = 'customer-name';

        $this->adminUsername = config('sftp.username');

        $this->sftpLogin();
    }

    protected function sftpLogin(): void
    {
        if (!$this->sftp->login(config('sftp.username'), $this->loadPrivateKey())) {
            throw new \Exception('SFTP login failed due to invalid credentials.');
        }
    }

    protected function loadPrivateKey(): AsymmetricKey
    {
        return PublicKeyLoader::load(base64_decode(config('sftp.private_key')));
    }

    public function createSftpUser(): bool
    {
        $created = false;

        if (!$this->userExist()) {
            $this->sftp->exec('sudo useradd -m ' . $this->customer);
            $created = true;
        }

        return $created;
    }

    public function sftpServiceStatus(): bool
    {
        if ($this->sftp->ping()) {
            return true;
        }
        return false;
    }

    protected function directoryExist(string $path): bool
    {
        if ($this->sftp->exec('sudo test -d "/home/' . $path . '" && echo "true" || echo "false"') == 'true') {
            return true;
        }
        return false;
    }

    protected function userExist(): bool
    {
        if ($this->sftp->exec("getent passwd | grep -c '^" . $this->customer . ":'") == 1) {
            return true;
        }
        return false;
    }

    public function setPublicSSHKey(string $key): bool
    {
        try {
            $this->createSSHDirectoryWithAdminPermissions();

            $this->sftp->exec('echo ' . $key . ' > /home/' . $this->customer . '/.ssh/authorized_keys');

            $this->setUserPermissions();

            return true;
        } catch (\Exception $exception) {
            return false;
        } finally {
            $this->sftp->disconnect();
        }
    }

    protected function createSSHDirectoryWithAdminPermissions(): void
    {
        $this->sftp->exec('sudo chgrp ' . $this->adminUsername . ' /home/' . $this->customer);

        if (!$this->directoryExist($this->customer . '/.ssh')) {
            $this->sftp->exec('sudo mkdir -p /home/' . $this->customer . '/.ssh');
        }

        $this->sftp->exec('sudo chown ' . $this->adminUsername . ' /home/' . $this->customer . '/.ssh');
    }

    public function removePublicSSHKey(): bool
    {
        try {
            $this->createSSHDirectoryWithAdminPermissions();

            $this->sftp->exec('rm /home/' . $this->customer . '/.ssh/authorized_keys');

            $this->setUserPermissions();

            return true;
        } catch (\Exception $exception) {
            return false;
        } finally {
            $this->sftp->disconnect();
        }
    }

    protected function setUserPermissions(): void
    {
        $this->sftp->exec('sudo chgrp -R ' . $this->customer . ' /home/' . $this->customer);
        $this->sftp->exec('sudo chown -R ' . $this->customer . ' /home/' . $this->customer );
    }

    public function getFileDetails(): array
    {
        $this->createSSHDirectoryWithAdminPermissions();

        if (!$this->sftp->file_exists('/home/' . $this->customer . '/.ssh/authorized_keys')) {
            return [];
        }

        $fileDetails = [
            'fingerprint' => $this->sftp->exec('sudo ssh-keygen -E md5 -lf "/home/spend-cloud/.ssh/authorized_keys" | awk \'{ print  }\' | cut -c 5-'),
            'created_date' => $this->sftp->exec('stat --printf="%z" /home/' . $this->customer . '/.ssh/authorized_keys'),
        ];

        $this->setUserPermissions();

        return $fileDetails;
    }
}

wipflash's avatar

I have figure out it here is the solution

public function testConstructWithSuccessLogin(){

        $sftp = \Mockery::mock(SFTP::class)->makePartial();

        $key = '-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUp
wmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ5
1s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQABAoGAFijko56+qGyN8M0RVyaRAXz++xTqHBLh
3tx4VgMtrQ+WEgCjhoTwo23KMBAuJGSYnRmoBZM3lMfTKevIkAidPExvYCdm5dYq3XToLkkLv5L2
pIIVOFMDG+KESnAFV7l2c+cnzRMW0+b6f8mR1CJzZuxVLL6Q02fvLi55/mbSYxECQQDeAw6fiIQX
GukBI4eMZZt4nscy2o12KyYner3VpoeE+Np2q+Z3pvAMd/aNzQ/W9WaI+NRfcxUJrmfPwIGm63il
AkEAxCL5HQb2bQr4ByorcMWm/hEP2MZzROV73yF41hPsRC9m66KrheO9HPTJuo3/9s5p+sqGxOlF
L0NDt4SkosjgGwJAFklyR1uZ/wPJjj611cdBcztlPdqoxssQGnh85BzCj/u3WqBpE2vjvyyvyI5k
X6zk7S0ljKtt2jny2+00VsBerQJBAJGC1Mg5Oydo5NwD6BiROrPxGo2bpTbu/fhrT8ebHkTz2epl
U9VQQSQzY1oZMVX8i1m5WUTLPz2yLJIBQVdXqhMCQBGoiuSoSjafUhV7i1cEGpb88h5NBYZzWXGZ
37sJ5QsW+sJyoNde3xH8vdXhzU7eT82D6X/scw9RZz+/6rCJ4p0=
-----END RSA PRIVATE KEY-----';

        \Config::set('sftp.private_key', base64_encode($key));
        \Config::set('sftp.username', 'sftp-user');

        $this->service->shouldReceive('loadPrivateKey')->withNoArgs()->andReturn('key');

        $this->service->shouldReceive('sftpLogin')->andThrow(\Exception::class);
        $sftp->shouldReceive('login')->andReturn(true);

        $sftpService = new SftpService($sftp);
        $this->assertSame($sftp, $sftpService->sftp);
    }

Please or to participate in this conversation.