@simplysm/storage 13.0.100 → 14.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -84
- package/dist/clients/ftp-storage-client.d.ts +18 -18
- package/dist/clients/ftp-storage-client.d.ts.map +1 -1
- package/dist/clients/ftp-storage-client.js +127 -116
- package/dist/clients/ftp-storage-client.js.map +1 -6
- package/dist/clients/sftp-storage-client.d.ts +14 -14
- package/dist/clients/sftp-storage-client.d.ts.map +1 -1
- package/dist/clients/sftp-storage-client.js +132 -116
- package/dist/clients/sftp-storage-client.js.map +1 -6
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -6
- package/dist/storage-factory.d.ts +5 -5
- package/dist/storage-factory.js +34 -30
- package/dist/storage-factory.js.map +1 -6
- package/dist/types/storage-conn-config.js +2 -1
- package/dist/types/storage-conn-config.js.map +1 -6
- package/dist/types/storage-type.js +2 -1
- package/dist/types/storage-type.js.map +1 -6
- package/dist/types/storage.js +2 -1
- package/dist/types/storage.js.map +1 -6
- package/package.json +6 -7
- package/src/clients/ftp-storage-client.ts +22 -22
- package/src/clients/sftp-storage-client.ts +28 -28
- package/src/index.ts +3 -3
- package/src/storage-factory.ts +6 -6
- package/tests/ftp-storage-client.spec.ts +0 -261
- package/tests/sftp-storage-client.spec.ts +0 -253
- package/tests/storage-factory.spec.ts +0 -172
|
@@ -1,32 +1,35 @@
|
|
|
1
1
|
import type { Bytes } from "@simplysm/core-common";
|
|
2
2
|
import { SdError } from "@simplysm/core-common";
|
|
3
3
|
import SftpClient from "ssh2-sftp-client";
|
|
4
|
+
import fsP from "fs/promises";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import pathMod from "path";
|
|
4
7
|
import type { StorageClient, FileInfo } from "../types/storage";
|
|
5
8
|
import type { StorageConnConfig } from "../types/storage-conn-config";
|
|
6
9
|
|
|
7
|
-
//
|
|
10
|
+
// ssh2-sftp-client 라이브러리 타입 정의에서의 Buffer 사용
|
|
8
11
|
type SftpGetResult = string | NodeJS.WritableStream | Bytes;
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
14
|
+
* SFTP 프로토콜을 사용하는 스토리지 클라이언트.
|
|
12
15
|
*
|
|
13
16
|
* @remarks
|
|
14
|
-
*
|
|
17
|
+
* 직접 사용하기보다 {@link StorageFactory.connect} 사용을 권장합니다.
|
|
15
18
|
*/
|
|
16
19
|
export class SftpStorageClient implements StorageClient {
|
|
17
20
|
private _client: SftpClient | undefined;
|
|
18
21
|
|
|
19
22
|
/**
|
|
20
|
-
*
|
|
23
|
+
* SFTP 서버에 연결합니다.
|
|
21
24
|
*
|
|
22
25
|
* @remarks
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
25
|
-
* -
|
|
26
|
+
* - 사용 후 {@link close}로 연결을 종료해야 합니다.
|
|
27
|
+
* - 동일 인스턴스에서 여러 번 호출하지 마세요 (연결 누수).
|
|
28
|
+
* - 자동 연결/종료 관리를 위해 {@link StorageFactory.connect} 사용을 권장합니다.
|
|
26
29
|
*/
|
|
27
30
|
async connect(config: StorageConnConfig): Promise<void> {
|
|
28
31
|
if (this._client !== undefined) {
|
|
29
|
-
throw new SdError("SFTP
|
|
32
|
+
throw new SdError("SFTP 서버에 이미 연결되어 있습니다. 먼저 close()를 호출해 주세요.");
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
const client = new SftpClient();
|
|
@@ -39,10 +42,7 @@ export class SftpStorageClient implements StorageClient {
|
|
|
39
42
|
password: config.password,
|
|
40
43
|
});
|
|
41
44
|
} else {
|
|
42
|
-
//
|
|
43
|
-
const fsP = await import("fs/promises");
|
|
44
|
-
const os = await import("os");
|
|
45
|
-
const pathMod = await import("path");
|
|
45
|
+
// SSH agent + 키 파일로 인증
|
|
46
46
|
const keyPath = pathMod.join(os.homedir(), ".ssh", "id_ed25519");
|
|
47
47
|
|
|
48
48
|
const baseOptions = {
|
|
@@ -58,7 +58,7 @@ export class SftpStorageClient implements StorageClient {
|
|
|
58
58
|
privateKey: await fsP.readFile(keyPath),
|
|
59
59
|
});
|
|
60
60
|
} catch {
|
|
61
|
-
// privateKey
|
|
61
|
+
// privateKey 파싱 실패 (암호화된 키 등) -> agent만으로 재시도
|
|
62
62
|
await client.connect(baseOptions);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -71,12 +71,12 @@ export class SftpStorageClient implements StorageClient {
|
|
|
71
71
|
|
|
72
72
|
private _requireClient(): SftpClient {
|
|
73
73
|
if (this._client === undefined) {
|
|
74
|
-
throw new SdError("
|
|
74
|
+
throw new SdError("SFTP 서버에 연결되어 있지 않습니다.");
|
|
75
75
|
}
|
|
76
76
|
return this._client;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
/**
|
|
79
|
+
/** 디렉토리를 생성합니다. 부모 디렉토리가 없으면 함께 생성합니다. */
|
|
80
80
|
async mkdir(dirPath: string): Promise<void> {
|
|
81
81
|
await this._requireClient().mkdir(dirPath, true);
|
|
82
82
|
}
|
|
@@ -86,16 +86,16 @@ export class SftpStorageClient implements StorageClient {
|
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
89
|
+
* 파일 또는 디렉토리의 존재 여부를 확인합니다.
|
|
90
90
|
*
|
|
91
91
|
* @remarks
|
|
92
|
-
*
|
|
93
|
-
*
|
|
92
|
+
* 부모 디렉토리가 존재하지 않아도 false를 반환합니다.
|
|
93
|
+
* 네트워크 오류, 권한 오류 등 모든 예외에 대해 false를 반환합니다.
|
|
94
94
|
*/
|
|
95
95
|
async exists(filePath: string): Promise<boolean> {
|
|
96
96
|
try {
|
|
97
|
-
// ssh2-sftp-client
|
|
98
|
-
// false:
|
|
97
|
+
// ssh2-sftp-client의 exists()는 false | 'd' | '-' | 'l'을 반환합니다.
|
|
98
|
+
// false: 존재하지 않음, 'd': 디렉토리, '-': 파일, 'l': 심볼릭 링크
|
|
99
99
|
const result = await this._requireClient().exists(filePath);
|
|
100
100
|
return typeof result === "string";
|
|
101
101
|
} catch {
|
|
@@ -112,24 +112,24 @@ export class SftpStorageClient implements StorageClient {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
async readFile(filePath: string): Promise<Bytes> {
|
|
115
|
-
// ssh2-sftp-client
|
|
116
|
-
//
|
|
115
|
+
// ssh2-sftp-client의 get()은 dst가 제공되지 않으면 Buffer를 반환합니다.
|
|
116
|
+
// 타입 정의(string | WritableStream | Buffer)와 달리 실제로는 Buffer만 반환됩니다.
|
|
117
117
|
const result = (await this._requireClient().get(filePath)) as SftpGetResult;
|
|
118
118
|
if (result instanceof Uint8Array) {
|
|
119
119
|
return result;
|
|
120
120
|
}
|
|
121
|
-
//
|
|
121
|
+
// 타입 정의상 string도 가능하므로 방어 코드
|
|
122
122
|
if (typeof result === "string") {
|
|
123
123
|
return new TextEncoder().encode(result);
|
|
124
124
|
}
|
|
125
|
-
throw new SdError("
|
|
125
|
+
throw new SdError("예상하지 못한 응답 타입입니다.");
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
async remove(filePath: string): Promise<void> {
|
|
129
129
|
await this._requireClient().delete(filePath);
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
/**
|
|
132
|
+
/** 로컬 파일 경로 또는 바이트 데이터를 원격 경로에 업로드합니다. */
|
|
133
133
|
async put(localPathOrBuffer: string | Bytes, storageFilePath: string): Promise<void> {
|
|
134
134
|
if (typeof localPathOrBuffer === "string") {
|
|
135
135
|
await this._requireClient().fastPut(localPathOrBuffer, storageFilePath);
|
|
@@ -144,11 +144,11 @@ export class SftpStorageClient implements StorageClient {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
|
-
*
|
|
147
|
+
* 연결을 종료합니다.
|
|
148
148
|
*
|
|
149
149
|
* @remarks
|
|
150
|
-
*
|
|
151
|
-
*
|
|
150
|
+
* 이미 종료된 상태에서 호출해도 안전합니다 (오류 미발생).
|
|
151
|
+
* 종료 후 동일 인스턴스에서 {@link connect}를 다시 호출하여 재연결할 수 있습니다.
|
|
152
152
|
*/
|
|
153
153
|
async close(): Promise<void> {
|
|
154
154
|
if (this._client === undefined) {
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
//
|
|
1
|
+
// 타입
|
|
2
2
|
export * from "./types/storage-conn-config";
|
|
3
3
|
export * from "./types/storage";
|
|
4
4
|
export * from "./types/storage-type";
|
|
5
5
|
|
|
6
|
-
//
|
|
6
|
+
// 클라이언트
|
|
7
7
|
export * from "./clients/ftp-storage-client";
|
|
8
8
|
export * from "./clients/sftp-storage-client";
|
|
9
9
|
|
|
10
|
-
//
|
|
10
|
+
// 팩토리
|
|
11
11
|
export * from "./storage-factory";
|
package/src/storage-factory.ts
CHANGED
|
@@ -5,17 +5,17 @@ import { FtpStorageClient } from "./clients/ftp-storage-client";
|
|
|
5
5
|
import { SftpStorageClient } from "./clients/sftp-storage-client";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
8
|
+
* 스토리지 클라이언트 팩토리
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* FTP, FTPS, SFTP 스토리지 연결을 생성하고 관리합니다.
|
|
11
11
|
*/
|
|
12
12
|
export class StorageFactory {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* 스토리지에 연결하고, 콜백을 실행한 후, 자동으로 연결을 종료합니다.
|
|
15
15
|
*
|
|
16
16
|
* @remarks
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* 콜백 패턴으로 연결/종료를 자동 관리하므로 직접 클라이언트를 사용하는 것보다 권장됩니다.
|
|
18
|
+
* 콜백에서 예외가 발생해도 연결은 자동으로 종료됩니다.
|
|
19
19
|
*/
|
|
20
20
|
static async connect<R>(
|
|
21
21
|
type: StorageProtocol,
|
|
@@ -29,7 +29,7 @@ export class StorageFactory {
|
|
|
29
29
|
return await fn(client);
|
|
30
30
|
} finally {
|
|
31
31
|
await client.close().catch(() => {
|
|
32
|
-
//
|
|
32
|
+
// 이미 종료된 경우 무시
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { FtpStorageClient } from "../src/clients/ftp-storage-client";
|
|
3
|
-
|
|
4
|
-
// Mock basic-ftp module
|
|
5
|
-
const mockAccess = vi.fn().mockResolvedValue(undefined);
|
|
6
|
-
const mockEnsureDir = vi.fn().mockResolvedValue(undefined);
|
|
7
|
-
const mockRename = vi.fn().mockResolvedValue(undefined);
|
|
8
|
-
const mockSize = vi.fn().mockResolvedValue(100);
|
|
9
|
-
const mockList = vi.fn().mockResolvedValue([
|
|
10
|
-
{ name: "file.txt", isFile: true },
|
|
11
|
-
{ name: "dir", isFile: false },
|
|
12
|
-
]);
|
|
13
|
-
const mockDownloadTo = vi.fn().mockImplementation((writable) => {
|
|
14
|
-
writable.emit("data", new TextEncoder().encode("test content"));
|
|
15
|
-
return Promise.resolve();
|
|
16
|
-
});
|
|
17
|
-
const mockRemove = vi.fn().mockResolvedValue(undefined);
|
|
18
|
-
const mockUploadFrom = vi.fn().mockResolvedValue(undefined);
|
|
19
|
-
const mockUploadFromDir = vi.fn().mockResolvedValue(undefined);
|
|
20
|
-
const mockClose = vi.fn();
|
|
21
|
-
|
|
22
|
-
vi.mock("basic-ftp", () => {
|
|
23
|
-
return {
|
|
24
|
-
default: {
|
|
25
|
-
Client: class MockClient {
|
|
26
|
-
access = mockAccess;
|
|
27
|
-
ensureDir = mockEnsureDir;
|
|
28
|
-
rename = mockRename;
|
|
29
|
-
size = mockSize;
|
|
30
|
-
list = mockList;
|
|
31
|
-
downloadTo = mockDownloadTo;
|
|
32
|
-
remove = mockRemove;
|
|
33
|
-
uploadFrom = mockUploadFrom;
|
|
34
|
-
uploadFromDir = mockUploadFromDir;
|
|
35
|
-
close = mockClose;
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe("FtpStorageClient", () => {
|
|
42
|
-
let client: FtpStorageClient;
|
|
43
|
-
|
|
44
|
-
beforeEach(() => {
|
|
45
|
-
vi.clearAllMocks();
|
|
46
|
-
client = new FtpStorageClient();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe("connect", () => {
|
|
50
|
-
it("Should connect with connection settings", async () => {
|
|
51
|
-
await client.connect({
|
|
52
|
-
host: "ftp.example.com",
|
|
53
|
-
port: 21,
|
|
54
|
-
user: "user",
|
|
55
|
-
password: "pass",
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
expect(mockAccess).toHaveBeenCalledWith({
|
|
59
|
-
host: "ftp.example.com",
|
|
60
|
-
port: 21,
|
|
61
|
-
user: "user",
|
|
62
|
-
password: "pass",
|
|
63
|
-
secure: false,
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("Should connect in secure mode", async () => {
|
|
68
|
-
const secureClient = new FtpStorageClient(true);
|
|
69
|
-
await secureClient.connect({ host: "ftp.example.com" });
|
|
70
|
-
|
|
71
|
-
expect(mockAccess).toHaveBeenCalledWith(expect.objectContaining({ secure: true }));
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("Should throw error when connect is called on already connected client", async () => {
|
|
75
|
-
await client.connect({ host: "test" });
|
|
76
|
-
await expect(client.connect({ host: "test" })).rejects.toThrow(
|
|
77
|
-
"FTP server is already connected. Please call close() first.",
|
|
78
|
-
);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("Should clean up client on connection failure", async () => {
|
|
82
|
-
mockAccess.mockRejectedValueOnce(new Error("Auth failed"));
|
|
83
|
-
await expect(client.connect({ host: "test" })).rejects.toThrow("Auth failed");
|
|
84
|
-
expect(mockClose).toHaveBeenCalled();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe("Method calls before connection", () => {
|
|
89
|
-
it("Should throw error when mkdir is called before connection", async () => {
|
|
90
|
-
await expect(client.mkdir("/test")).rejects.toThrow("Not connected to FTP server.");
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("Should throw error when rename is called before connection", async () => {
|
|
94
|
-
await expect(client.rename("/from", "/to")).rejects.toThrow(
|
|
95
|
-
"Not connected to FTP server.",
|
|
96
|
-
);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("Should throw error when list is called before connection", async () => {
|
|
100
|
-
await expect(client.list("/")).rejects.toThrow("Not connected to FTP server.");
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
describe("mkdir", () => {
|
|
105
|
-
it("Should create directory", async () => {
|
|
106
|
-
await client.connect({ host: "test" });
|
|
107
|
-
await client.mkdir("/test/dir");
|
|
108
|
-
|
|
109
|
-
expect(mockEnsureDir).toHaveBeenCalledWith("/test/dir");
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
describe("rename", () => {
|
|
114
|
-
it("Should rename file/directory", async () => {
|
|
115
|
-
await client.connect({ host: "test" });
|
|
116
|
-
await client.rename("/from", "/to");
|
|
117
|
-
|
|
118
|
-
expect(mockRename).toHaveBeenCalledWith("/from", "/to");
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe("list", () => {
|
|
123
|
-
it("Should return directory list as FileInfo array", async () => {
|
|
124
|
-
await client.connect({ host: "test" });
|
|
125
|
-
const result = await client.list("/");
|
|
126
|
-
|
|
127
|
-
expect(result).toEqual([
|
|
128
|
-
{ name: "file.txt", isFile: true },
|
|
129
|
-
{ name: "dir", isFile: false },
|
|
130
|
-
]);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
describe("readFile", () => {
|
|
135
|
-
it("Should return file content as Uint8Array", async () => {
|
|
136
|
-
await client.connect({ host: "test" });
|
|
137
|
-
const result = await client.readFile("/file.txt");
|
|
138
|
-
|
|
139
|
-
expect(result).toBeInstanceOf(Uint8Array);
|
|
140
|
-
expect(new TextDecoder().decode(result)).toBe("test content");
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe("exists", () => {
|
|
145
|
-
it("Should return true if file exists (checked via size)", async () => {
|
|
146
|
-
await client.connect({ host: "test" });
|
|
147
|
-
const result = await client.exists("/path/file.txt");
|
|
148
|
-
|
|
149
|
-
expect(mockSize).toHaveBeenCalledWith("/path/file.txt");
|
|
150
|
-
expect(result).toBe(true);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("Should return true if directory exists (checked via list)", async () => {
|
|
154
|
-
mockSize.mockRejectedValueOnce(new Error("Not a file"));
|
|
155
|
-
await client.connect({ host: "test" });
|
|
156
|
-
const result = await client.exists("/path/dir");
|
|
157
|
-
|
|
158
|
-
expect(mockList).toHaveBeenCalledWith("/path");
|
|
159
|
-
expect(result).toBe(true);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it("Should return false if file does not exist", async () => {
|
|
163
|
-
mockSize.mockRejectedValueOnce(new Error("Not a file"));
|
|
164
|
-
mockList.mockResolvedValueOnce([]);
|
|
165
|
-
await client.connect({ host: "test" });
|
|
166
|
-
const result = await client.exists("/path/nonexistent.txt");
|
|
167
|
-
|
|
168
|
-
expect(result).toBe(false);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("Should return false on error", async () => {
|
|
172
|
-
mockSize.mockRejectedValueOnce(new Error("Not a file"));
|
|
173
|
-
mockList.mockRejectedValueOnce(new Error("Not found"));
|
|
174
|
-
await client.connect({ host: "test" });
|
|
175
|
-
const result = await client.exists("/path/error.txt");
|
|
176
|
-
|
|
177
|
-
expect(result).toBe(false);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it("Should check file existence in root directory", async () => {
|
|
181
|
-
mockSize.mockRejectedValueOnce(new Error("Not a file"));
|
|
182
|
-
await client.connect({ host: "test" });
|
|
183
|
-
const result = await client.exists("/file.txt");
|
|
184
|
-
|
|
185
|
-
expect(mockList).toHaveBeenCalledWith("/");
|
|
186
|
-
expect(result).toBe(true);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it("Should check file existence for paths without slashes", async () => {
|
|
190
|
-
mockSize.mockRejectedValueOnce(new Error("Not a file"));
|
|
191
|
-
await client.connect({ host: "test" });
|
|
192
|
-
const result = await client.exists("file.txt");
|
|
193
|
-
|
|
194
|
-
expect(mockList).toHaveBeenCalledWith("/");
|
|
195
|
-
expect(result).toBe(true);
|
|
196
|
-
});
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
describe("remove", () => {
|
|
200
|
-
it("Should delete file", async () => {
|
|
201
|
-
await client.connect({ host: "test" });
|
|
202
|
-
await client.remove("/file.txt");
|
|
203
|
-
|
|
204
|
-
expect(mockRemove).toHaveBeenCalledWith("/file.txt");
|
|
205
|
-
});
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
describe("put", () => {
|
|
209
|
-
it("Should upload from local path", async () => {
|
|
210
|
-
await client.connect({ host: "test" });
|
|
211
|
-
await client.put("/local/file.txt", "/remote/file.txt");
|
|
212
|
-
|
|
213
|
-
expect(mockUploadFrom).toHaveBeenCalledWith("/local/file.txt", "/remote/file.txt");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("Should upload from Uint8Array", async () => {
|
|
217
|
-
await client.connect({ host: "test" });
|
|
218
|
-
const bytes = new TextEncoder().encode("content");
|
|
219
|
-
await client.put(bytes, "/remote/file.txt");
|
|
220
|
-
|
|
221
|
-
expect(mockUploadFrom).toHaveBeenCalled();
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
describe("uploadDir", () => {
|
|
226
|
-
it("Should upload directory", async () => {
|
|
227
|
-
await client.connect({ host: "test" });
|
|
228
|
-
await client.uploadDir("/local/dir", "/remote/dir");
|
|
229
|
-
|
|
230
|
-
expect(mockUploadFromDir).toHaveBeenCalledWith("/local/dir", "/remote/dir");
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
describe("close", () => {
|
|
235
|
-
it("Should exit without error when close is called before connection", async () => {
|
|
236
|
-
await expect(client.close()).resolves.toBeUndefined();
|
|
237
|
-
});
|
|
238
|
-
|
|
239
|
-
it("Should close connection", async () => {
|
|
240
|
-
await client.connect({ host: "test" });
|
|
241
|
-
await client.close();
|
|
242
|
-
|
|
243
|
-
expect(mockClose).toHaveBeenCalled();
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it("Should throw error when calling method after close", async () => {
|
|
247
|
-
await client.connect({ host: "test" });
|
|
248
|
-
await client.close();
|
|
249
|
-
|
|
250
|
-
await expect(client.mkdir("/test")).rejects.toThrow("Not connected to FTP server.");
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it("Should allow reconnection after close", async () => {
|
|
254
|
-
await client.connect({ host: "test" });
|
|
255
|
-
await client.close();
|
|
256
|
-
await client.connect({ host: "test" });
|
|
257
|
-
|
|
258
|
-
expect(mockAccess).toHaveBeenCalledTimes(2);
|
|
259
|
-
});
|
|
260
|
-
});
|
|
261
|
-
});
|