Skip to content

Commit 1270c3a

Browse files
committed
SftpExecutorTest added
1 parent c872861 commit 1270c3a

2 files changed

Lines changed: 211 additions & 2 deletions

File tree

tests/classes/RemoteExecutorTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public function testConstructorWithPassword(): void
4444
$password = 'secret';
4545

4646
config()->set("syncops.connections.$server", [
47-
'host' => 'example.com',
47+
'host' => 'example.com',
4848
'username' => 'user',
4949
'password' => $password,
5050
'key_path' => '',
@@ -74,7 +74,7 @@ public function testConstructorWithPublicKey(): void
7474
file_put_contents($keyPath, 'FAKE_KEY');
7575

7676
config()->set("syncops.connections.$server", [
77-
'host' => 'example.com',
77+
'host' => 'example.com',
7878
'username' => 'user',
7979
'password' => '',
8080
'key_path' => $keyPath,

tests/classes/SftpExecutorTest.php

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
<?php namespace NumenCode\SyncOps\Tests\Classes;
2+
3+
use Mockery;
4+
use PluginTestCase;
5+
use phpseclib3\Net\SFTP;
6+
use NumenCode\SyncOps\Classes\SftpExecutor;
7+
8+
class SftpExecutorTest extends PluginTestCase
9+
{
10+
protected string $server = 'production';
11+
protected array $config;
12+
protected string $credentials = 'password';
13+
14+
public function setUp(): void
15+
{
16+
parent::setUp();
17+
18+
$this->config = [
19+
'host' => 'example.com',
20+
'port' => 22,
21+
'username' => 'user',
22+
];
23+
}
24+
25+
public function tearDown(): void
26+
{
27+
Mockery::close();
28+
29+
parent::tearDown();
30+
}
31+
32+
public function testConnectSuccess(): void
33+
{
34+
// This will not assert login() directly, since connect() creates its own SFTP internally.
35+
// We'll just mock the class partially and assert we get an SFTP instance back.
36+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])
37+
->makePartial()
38+
->shouldAllowMockingProtectedMethods();
39+
40+
// We cannot intercept internal new SFTP, so we just call it and assert successful type.
41+
try {
42+
$result = $executor->connect();
43+
} catch (\Throwable $e) {
44+
$this->markTestSkipped('Real SFTP connection not available in test environment.');
45+
return;
46+
}
47+
48+
$this->assertInstanceOf(SFTP::class, $result);
49+
}
50+
51+
public function testConnectFailureThrowsException(): void
52+
{
53+
// Expect *any* exception on bad connection (phpseclib typically throws RuntimeException or Error)
54+
$executor = new SftpExecutor($this->server, [
55+
'host' => 'nonexistent-host.invalid',
56+
'port' => 22,
57+
'username' => 'user',
58+
], 'wrong-password');
59+
60+
$this->expectException(\Throwable::class);
61+
$this->expectExceptionMessageMatches('/(SFTP|connect|failed)/i');
62+
63+
$executor->connect();
64+
}
65+
66+
public function testUploadSuccess(): void
67+
{
68+
$localFile = __DIR__ . '/_fixture_local.txt';
69+
file_put_contents($localFile, 'content');
70+
$remoteFile = '/remote/file.txt';
71+
72+
$sftpMock = Mockery::mock(SFTP::class);
73+
$sftpMock->shouldReceive('put')
74+
->once()
75+
->with($remoteFile, 'content')
76+
->andReturnTrue();
77+
78+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
79+
$executor->shouldReceive('connect')->andReturn($sftpMock);
80+
81+
$executor->upload($localFile, $remoteFile);
82+
83+
unlink($localFile);
84+
85+
$this->assertTrue(true);
86+
}
87+
88+
public function testUploadFailureThrowsException(): void
89+
{
90+
$localFile = __DIR__ . '/_fixture_local.txt';
91+
file_put_contents($localFile, 'data');
92+
$remoteFile = '/remote/file.txt';
93+
94+
$sftpMock = Mockery::mock(SFTP::class);
95+
$sftpMock->shouldReceive('put')
96+
->once()
97+
->andReturnFalse();
98+
99+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
100+
$executor->shouldReceive('connect')->andReturn($sftpMock);
101+
102+
$this->expectException(\RuntimeException::class);
103+
$executor->upload($localFile, $remoteFile);
104+
105+
unlink($localFile);
106+
}
107+
108+
public function testDownloadSuccess(): void
109+
{
110+
$localFile = __DIR__ . '/_downloaded.txt';
111+
$remoteFile = '/remote/file.txt';
112+
113+
$sftpMock = Mockery::mock(SFTP::class);
114+
$sftpMock->shouldReceive('get')
115+
->once()
116+
->with($remoteFile, $localFile)
117+
->andReturnTrue();
118+
119+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
120+
$executor->shouldReceive('connect')->andReturn($sftpMock);
121+
122+
$executor->download($remoteFile, $localFile);
123+
124+
$this->assertTrue(true);
125+
}
126+
127+
public function testDownloadFailureThrowsException(): void
128+
{
129+
$localFile = __DIR__ . '/_downloaded.txt';
130+
$remoteFile = '/remote/file.txt';
131+
132+
$sftpMock = Mockery::mock(SFTP::class);
133+
$sftpMock->shouldReceive('get')->once()->andReturnFalse();
134+
135+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
136+
$executor->shouldReceive('connect')->andReturn($sftpMock);
137+
138+
$this->expectException(\RuntimeException::class);
139+
$executor->download($remoteFile, $localFile);
140+
}
141+
142+
public function testListFilesRecursivelyFiltersEntries(): void
143+
{
144+
$entries = [
145+
'./file1.txt',
146+
'./folder/file2.txt',
147+
'./thumb/img.jpg',
148+
'./.env',
149+
'./.gitignore',
150+
'./folder/../ignored',
151+
'./resized/photo.png',
152+
];
153+
154+
$sftpMock = Mockery::mock(SFTP::class);
155+
$sftpMock->shouldReceive('nlist')->once()->andReturn($entries);
156+
$sftpMock->shouldReceive('is_file')->andReturnUsing(function ($file) {
157+
return !str_contains($file, 'ignored');
158+
});
159+
160+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
161+
$executor->shouldReceive('connect')->andReturn($sftpMock);
162+
163+
$result = $executor->listFilesRecursively('/remote');
164+
165+
$expected = [
166+
'/remote/file1.txt',
167+
'/remote/folder/file2.txt',
168+
'/remote/.gitignore',
169+
];
170+
171+
sort($result);
172+
sort($expected);
173+
174+
$this->assertEquals($expected, $result);
175+
}
176+
177+
public function testFilesizeRemoteReturnsInt(): void
178+
{
179+
$sftpMock = Mockery::mock(SFTP::class);
180+
$sftpMock->shouldReceive('filesize')->once()->with('/remote/file.txt')->andReturn(1234);
181+
182+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
183+
$executor->shouldReceive('connect')->andReturn($sftpMock);
184+
185+
$this->assertSame(1234, $executor->filesizeRemote('/remote/file.txt'));
186+
}
187+
188+
public function testFilesizeRemoteReturnsNullOnFailure(): void
189+
{
190+
$sftpMock = Mockery::mock(SFTP::class);
191+
$sftpMock->shouldReceive('filesize')->once()->andReturn(false);
192+
193+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
194+
$executor->shouldReceive('connect')->andReturn($sftpMock);
195+
196+
$this->assertNull($executor->filesizeRemote('/remote/missing.txt'));
197+
}
198+
199+
public function testExistsDelegatesToSftp(): void
200+
{
201+
$sftpMock = Mockery::mock(SFTP::class);
202+
$sftpMock->shouldReceive('file_exists')->once()->with('/remote/test.txt')->andReturnTrue();
203+
204+
$executor = Mockery::mock(SftpExecutor::class, [$this->server, $this->config, $this->credentials])->makePartial();
205+
$executor->shouldReceive('connect')->andReturn($sftpMock);
206+
207+
$this->assertTrue($executor->exists('/remote/test.txt'));
208+
}
209+
}

0 commit comments

Comments
 (0)