@scaleway/sdk-client 2.2.1 → 2.3.0
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/dist/_virtual/_rolldown/runtime.js +2 -0
- package/dist/bridge.js +8 -0
- package/dist/helpers/__tests__/is-browser.browser.test.js +10 -0
- package/dist/helpers/__tests__/is-browser.node.test.js +10 -0
- package/dist/helpers/__tests__/json.test.js +48 -0
- package/dist/helpers/__tests__/marshalling.test.js +177 -0
- package/dist/helpers/is-browser.js +3 -1
- package/dist/helpers/is-response.js +3 -1
- package/dist/helpers/json.d.ts +1 -1
- package/dist/helpers/json.js +10 -4
- package/dist/helpers/marshalling.d.ts +1 -1
- package/dist/helpers/marshalling.js +7 -5
- package/dist/index.js +4 -4
- package/dist/internal/async/__tests__/interval-retrier.test.js +121 -0
- package/dist/internal/async/__tests__/sleep.test.js +19 -0
- package/dist/internal/async/interval-retrier.d.ts +2 -2
- package/dist/internal/async/interval-retrier.js +33 -3
- package/dist/internal/async/sleep.js +3 -1
- package/dist/internal/interceptors/__tests__/composer.test.js +47 -0
- package/dist/internal/interceptors/__tests__/helpers.test.js +27 -0
- package/dist/internal/interceptors/composer.js +5 -3
- package/dist/internal/interceptors/helpers.d.ts +1 -1
- package/dist/internal/interceptors/helpers.js +4 -2
- package/dist/internal/interceptors/types.js +0 -0
- package/dist/internal/logger/__tests__/index.test.js +209 -0
- package/dist/internal/logger/console-logger.js +6 -5
- package/dist/internal/logger/index.d.ts +1 -2
- package/dist/internal/logger/index.js +5 -3
- package/dist/internal/logger/level-resolver.d.ts +1 -1
- package/dist/internal/logger/level-resolver.js +4 -2
- package/dist/internal/logger/logger.js +0 -0
- package/dist/internal/validations/__tests__/string-validation.test.js +98 -0
- package/dist/internal/validations/string-validation.js +14 -9
- package/dist/internals.js +9 -8
- package/dist/package.js +6 -4
- package/dist/scw/__tests__/api.test.js +22 -0
- package/dist/scw/__tests__/auth.test.js +57 -0
- package/dist/scw/__tests__/client-ini-factory.test.js +220 -0
- package/dist/scw/__tests__/client-ini-profile.test.js +70 -0
- package/dist/scw/__tests__/client-settings.test.js +51 -0
- package/dist/scw/__tests__/client.test.js +59 -0
- package/dist/scw/__tests__/custom-marshalling.test.js +168 -0
- package/dist/scw/api.js +2 -0
- package/dist/scw/auth.js +17 -6
- package/dist/scw/client-ini-factory.d.ts +1 -2
- package/dist/scw/client-ini-factory.js +9 -7
- package/dist/scw/client-ini-profile.js +3 -1
- package/dist/scw/client-settings.js +3 -1
- package/dist/scw/client.js +4 -2
- package/dist/scw/constants.js +6 -4
- package/dist/scw/custom-marshalling.d.ts +1 -1
- package/dist/scw/custom-marshalling.js +17 -15
- package/dist/scw/custom-types.js +4 -3
- package/dist/scw/errors/__tests__/scw-error.test.js +41 -0
- package/dist/scw/errors/__tests__/types.test.js +16 -0
- package/dist/scw/errors/error-parser.js +3 -1
- package/dist/scw/errors/non-standard/__tests__/index.test.js +123 -0
- package/dist/scw/errors/non-standard/invalid-request-mapper.d.ts +1 -1
- package/dist/scw/errors/non-standard/invalid-request-mapper.js +3 -1
- package/dist/scw/errors/non-standard/unknown-resource-mapper.js +3 -1
- package/dist/scw/errors/scw-error-from-json.js +0 -0
- package/dist/scw/errors/scw-error.js +2 -2
- package/dist/scw/errors/standard/__tests__/index.test.js +329 -0
- package/dist/scw/errors/standard/already-exists-error.js +2 -0
- package/dist/scw/errors/standard/denied-authentication-error.js +2 -0
- package/dist/scw/errors/standard/index.js +3 -1
- package/dist/scw/errors/standard/invalid-arguments-error.js +2 -0
- package/dist/scw/errors/standard/out-of-stock-error.js +2 -0
- package/dist/scw/errors/standard/permissions-denied-error.js +2 -0
- package/dist/scw/errors/standard/precondition-failed-error.js +2 -0
- package/dist/scw/errors/standard/quotas-exceeded-error.js +2 -0
- package/dist/scw/errors/standard/resource-expired-error.js +2 -0
- package/dist/scw/errors/standard/resource-locked-error.js +2 -0
- package/dist/scw/errors/standard/resource-not-found-error.js +2 -0
- package/dist/scw/errors/standard/too-many-requests-error.js +2 -0
- package/dist/scw/errors/standard/transient-state-error.js +2 -0
- package/dist/scw/errors/types.js +3 -1
- package/dist/scw/fetch/__tests__/build-fetcher.test.js +114 -0
- package/dist/scw/fetch/__tests__/http-dumper.test.js +45 -0
- package/dist/scw/fetch/__tests__/http-interceptors.test.js +60 -0
- package/dist/scw/fetch/__tests__/resource-paginator.test.js +110 -0
- package/dist/scw/fetch/__tests__/response-parser.test.js +94 -0
- package/dist/scw/fetch/build-fetcher.js +6 -4
- package/dist/scw/fetch/http-dumper.js +4 -2
- package/dist/scw/fetch/http-interceptors.js +5 -3
- package/dist/scw/fetch/resource-paginator.d.ts +3 -3
- package/dist/scw/fetch/resource-paginator.js +6 -4
- package/dist/scw/fetch/response-parser.d.ts +1 -1
- package/dist/scw/fetch/response-parser.js +5 -3
- package/dist/scw/fetch/types.js +0 -0
- package/dist/scw/locality.js +2 -0
- package/dist/vendor/base64/index.d.js +0 -0
- package/dist/vendor/base64/index.js +2 -0
- package/package.json +6 -3
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { authenticateWithSecrets, authenticateWithSessionToken, obfuscateAuthHeadersEntry, obfuscateToken, obfuscateUUID } from "../auth.js";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
//#region src/scw/__tests__/auth.test.ts
|
|
4
|
+
describe("obfuscateToken", () => {
|
|
5
|
+
it("hides anything after 5 characters", () => {
|
|
6
|
+
expect(obfuscateToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c")).toBe("eyJhbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
|
7
|
+
expect(obfuscateToken("")).toBe("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
describe("obfuscateUUID", () => {
|
|
11
|
+
it("hides anything after 8 characters", () => {
|
|
12
|
+
expect(obfuscateUUID("db31db7b-473d-488e-bd2e-1b77ee426910")).toBe("db31db7b-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
|
|
13
|
+
expect(obfuscateUUID("")).toBe("-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe("obfuscateAuthHeadersEntry", () => {
|
|
17
|
+
it("obfuscates an auth token", () => {
|
|
18
|
+
expect(obfuscateAuthHeadersEntry(["x-auth-token", "db31db7b-473d-488e-bd2e-1b77ee426910"])).toStrictEqual(["x-auth-token", "db31db7b-xxxx-xxxx-xxxx-xxxxxxxxxxxx"]);
|
|
19
|
+
});
|
|
20
|
+
it("obfuscates a session token", () => {
|
|
21
|
+
expect(obfuscateAuthHeadersEntry(["x-session-token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"])).toStrictEqual(["x-session-token", "eyJhbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"]);
|
|
22
|
+
});
|
|
23
|
+
it(`doesn't obfuscate unknown key`, () => {
|
|
24
|
+
expect(obfuscateAuthHeadersEntry(["x-unknown-key", "random-value"])).toStrictEqual(["x-unknown-key", "random-value"]);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
describe("authenticateWithSessionToken", () => {
|
|
28
|
+
it("retrieves the token and updates the request header", async () => {
|
|
29
|
+
const dummyToken = "dummy";
|
|
30
|
+
const sourceReq = new Request("https://api.scaleway.com/my/path");
|
|
31
|
+
const updatedReq = await authenticateWithSessionToken(() => Promise.resolve(dummyToken))({ request: sourceReq });
|
|
32
|
+
const expectedReq = sourceReq.clone();
|
|
33
|
+
expectedReq.headers.append("x-session-token", "dummy");
|
|
34
|
+
expect(updatedReq).toMatchObject(expectedReq);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe("authenticateWithSecrets", () => {
|
|
38
|
+
const sourceReq = new Request("https://api.scaleway.com/my/path");
|
|
39
|
+
it("updates the request header with the valid secrets", async () => {
|
|
40
|
+
const validSecrets = {
|
|
41
|
+
accessKey: "SCW01234567890123456",
|
|
42
|
+
secretKey: "e4b83996-4c60-449a-98d2-38f5de7b4e6b"
|
|
43
|
+
};
|
|
44
|
+
const updatedReq = await authenticateWithSecrets(validSecrets)({ request: sourceReq });
|
|
45
|
+
const expectedReq = sourceReq.clone();
|
|
46
|
+
expectedReq.headers.append("x-auth-token", validSecrets.secretKey);
|
|
47
|
+
expect(updatedReq).toMatchObject(expectedReq);
|
|
48
|
+
});
|
|
49
|
+
it("throws an exception for invalid secrets", () => {
|
|
50
|
+
const invalidSecrets = {
|
|
51
|
+
accessKey: "",
|
|
52
|
+
secretKey: ""
|
|
53
|
+
};
|
|
54
|
+
expect(() => authenticateWithSecrets(invalidSecrets)({ request: sourceReq })).toThrow();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
//#endregion
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { withAdditionalInterceptors, withDefaultPageSize, withHTTPClient, withLegacyInterceptors, withProfile, withUserAgent, withUserAgentSuffix } from "../client-ini-factory.js";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
//#region src/scw/__tests__/client-ini-factory.test.ts
|
|
4
|
+
var EMPTY_PROFILE = {};
|
|
5
|
+
var FILLED_PROFILE = {
|
|
6
|
+
accessKey: "SCW1234567890ABCDEFG",
|
|
7
|
+
apiURL: "https://api.example.com",
|
|
8
|
+
defaultOrganizationId: "a8f9d4ab-dda4-4b9d-ab77-0a8c59a246e9",
|
|
9
|
+
defaultProjectId: "d67e2125-a1ee-4056-9f7a-ab855be03d07",
|
|
10
|
+
defaultRegion: "fr-par",
|
|
11
|
+
defaultZone: "fr-par-3",
|
|
12
|
+
secretKey: "3aef5281-13eb-4705-b858-eb64dd5da24c"
|
|
13
|
+
};
|
|
14
|
+
var DEFAULT_SETTINGS = {
|
|
15
|
+
apiURL: "https://api.scaleway.com",
|
|
16
|
+
defaultOrganizationId: "bd61983a-e4b5-4c6b-a841-07bc44037b5a",
|
|
17
|
+
defaultPageSize: 10,
|
|
18
|
+
defaultProjectId: "43f42be7-ea3d-4148-889c-3ad4b53bddcb",
|
|
19
|
+
defaultRegion: "nl-ams",
|
|
20
|
+
defaultZone: "fr-par-1",
|
|
21
|
+
httpClient: fetch,
|
|
22
|
+
interceptors: [],
|
|
23
|
+
userAgent: "scaleway-sdk-js/v1.0.0-beta"
|
|
24
|
+
};
|
|
25
|
+
describe("withProfile", () => {
|
|
26
|
+
it(`doesn't modify Settings object with empty Profile object`, () => {
|
|
27
|
+
expect(withProfile(EMPTY_PROFILE)(DEFAULT_SETTINGS)).toStrictEqual(DEFAULT_SETTINGS);
|
|
28
|
+
expect(withProfile({ WTF: "malicious content" })(DEFAULT_SETTINGS)).toStrictEqual(DEFAULT_SETTINGS);
|
|
29
|
+
expect(withProfile({
|
|
30
|
+
apiURL: void 0,
|
|
31
|
+
defaultOrganizationId: void 0,
|
|
32
|
+
defaultPageSize: void 0,
|
|
33
|
+
defaultProjectId: void 0,
|
|
34
|
+
defaultRegion: void 0,
|
|
35
|
+
defaultZone: void 0,
|
|
36
|
+
httpClient: void 0,
|
|
37
|
+
interceptors: void 0,
|
|
38
|
+
requestInterceptors: void 0,
|
|
39
|
+
responseInterceptors: void 0,
|
|
40
|
+
userAgent: void 0
|
|
41
|
+
})(DEFAULT_SETTINGS)).toStrictEqual(DEFAULT_SETTINGS);
|
|
42
|
+
expect(withProfile({
|
|
43
|
+
apiURL: null,
|
|
44
|
+
defaultOrganizationId: null,
|
|
45
|
+
defaultPageSize: null,
|
|
46
|
+
defaultProjectId: null,
|
|
47
|
+
defaultRegion: null,
|
|
48
|
+
defaultZone: null,
|
|
49
|
+
httpClient: null,
|
|
50
|
+
interceptors: null,
|
|
51
|
+
requestInterceptors: null,
|
|
52
|
+
responseInterceptors: null,
|
|
53
|
+
userAgent: null
|
|
54
|
+
})(DEFAULT_SETTINGS)).toStrictEqual(DEFAULT_SETTINGS);
|
|
55
|
+
expect(withProfile({
|
|
56
|
+
apiURL: "",
|
|
57
|
+
defaultOrganizationId: "",
|
|
58
|
+
defaultPageSize: "",
|
|
59
|
+
defaultProjectId: "",
|
|
60
|
+
defaultRegion: "",
|
|
61
|
+
defaultZone: "",
|
|
62
|
+
httpClient: "",
|
|
63
|
+
interceptors: "",
|
|
64
|
+
requestInterceptors: "",
|
|
65
|
+
responseInterceptors: "",
|
|
66
|
+
userAgent: ""
|
|
67
|
+
})(DEFAULT_SETTINGS)).toStrictEqual(DEFAULT_SETTINGS);
|
|
68
|
+
expect(withProfile({
|
|
69
|
+
apiURL: 0,
|
|
70
|
+
defaultOrganizationId: 0,
|
|
71
|
+
defaultPageSize: 0,
|
|
72
|
+
defaultProjectId: 0,
|
|
73
|
+
defaultRegion: 0,
|
|
74
|
+
defaultZone: 0,
|
|
75
|
+
httpClient: 0,
|
|
76
|
+
interceptors: 0,
|
|
77
|
+
requestInterceptors: 0,
|
|
78
|
+
responseInterceptors: 0,
|
|
79
|
+
userAgent: 0
|
|
80
|
+
})(DEFAULT_SETTINGS)).toStrictEqual(DEFAULT_SETTINGS);
|
|
81
|
+
});
|
|
82
|
+
it("only modifies apiURL", () => {
|
|
83
|
+
const expectedSettings = {
|
|
84
|
+
...DEFAULT_SETTINGS,
|
|
85
|
+
apiURL: FILLED_PROFILE.apiURL
|
|
86
|
+
};
|
|
87
|
+
expect(withProfile({ apiURL: FILLED_PROFILE.apiURL })(DEFAULT_SETTINGS)).toStrictEqual(expectedSettings);
|
|
88
|
+
});
|
|
89
|
+
it("only modifies default organization ID", () => {
|
|
90
|
+
const expectedSettings = {
|
|
91
|
+
...DEFAULT_SETTINGS,
|
|
92
|
+
defaultOrganizationId: FILLED_PROFILE.defaultOrganizationId
|
|
93
|
+
};
|
|
94
|
+
expect(withProfile({ defaultOrganizationId: FILLED_PROFILE.defaultOrganizationId })(DEFAULT_SETTINGS)).toStrictEqual(expectedSettings);
|
|
95
|
+
});
|
|
96
|
+
it("only modifies default project ID", () => {
|
|
97
|
+
const expectedSettings = {
|
|
98
|
+
...DEFAULT_SETTINGS,
|
|
99
|
+
defaultProjectId: FILLED_PROFILE.defaultProjectId
|
|
100
|
+
};
|
|
101
|
+
expect(withProfile({ defaultProjectId: FILLED_PROFILE.defaultProjectId })(DEFAULT_SETTINGS)).toStrictEqual(expectedSettings);
|
|
102
|
+
});
|
|
103
|
+
it("only modifies default region", () => {
|
|
104
|
+
const expectedSettings = {
|
|
105
|
+
...DEFAULT_SETTINGS,
|
|
106
|
+
defaultRegion: FILLED_PROFILE.defaultRegion
|
|
107
|
+
};
|
|
108
|
+
expect(withProfile({ defaultRegion: FILLED_PROFILE.defaultRegion })(DEFAULT_SETTINGS)).toStrictEqual(expectedSettings);
|
|
109
|
+
});
|
|
110
|
+
it("only modifies default zone", () => {
|
|
111
|
+
const expectedSettings = {
|
|
112
|
+
...DEFAULT_SETTINGS,
|
|
113
|
+
defaultZone: FILLED_PROFILE.defaultZone
|
|
114
|
+
};
|
|
115
|
+
expect(withProfile({ defaultZone: FILLED_PROFILE.defaultZone })(DEFAULT_SETTINGS)).toStrictEqual(expectedSettings);
|
|
116
|
+
});
|
|
117
|
+
it("modifies authentication", async () => {
|
|
118
|
+
if (!DEFAULT_SETTINGS.apiURL) throw new Error("API URL is missing");
|
|
119
|
+
const request = new Request(DEFAULT_SETTINGS.apiURL);
|
|
120
|
+
const reqInterceptor = withProfile({
|
|
121
|
+
accessKey: FILLED_PROFILE.accessKey,
|
|
122
|
+
secretKey: FILLED_PROFILE.secretKey
|
|
123
|
+
})(DEFAULT_SETTINGS).interceptors[0].request;
|
|
124
|
+
expect(reqInterceptor).toBeDefined();
|
|
125
|
+
if (reqInterceptor) {
|
|
126
|
+
const { headers } = await reqInterceptor({ request });
|
|
127
|
+
expect(headers.get("x-auth-token")).toStrictEqual(FILLED_PROFILE.secretKey);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
describe("withDefaultPageSize", () => {
|
|
132
|
+
it("only modifies the default page size", () => {
|
|
133
|
+
const newDefaultPageSize = 42;
|
|
134
|
+
const expectedSettings = {
|
|
135
|
+
...DEFAULT_SETTINGS,
|
|
136
|
+
defaultPageSize: newDefaultPageSize
|
|
137
|
+
};
|
|
138
|
+
expect(JSON.stringify(withDefaultPageSize(newDefaultPageSize)(DEFAULT_SETTINGS))).toStrictEqual(JSON.stringify(expectedSettings));
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe("withHTTPClient", () => {
|
|
142
|
+
it("only modifies the http client", () => {
|
|
143
|
+
const newHTTPClient = () => Promise.resolve(new Response());
|
|
144
|
+
const expectedSettings = {
|
|
145
|
+
...DEFAULT_SETTINGS,
|
|
146
|
+
httpClient: newHTTPClient
|
|
147
|
+
};
|
|
148
|
+
expect(JSON.stringify(withHTTPClient(newHTTPClient)(DEFAULT_SETTINGS))).toStrictEqual(JSON.stringify(expectedSettings));
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
describe("withUserAgent", () => {
|
|
152
|
+
const defaultUserAgent = "my-source/v0.1.0";
|
|
153
|
+
it("only modifies the user agent", () => {
|
|
154
|
+
const expectedSettings = {
|
|
155
|
+
...DEFAULT_SETTINGS,
|
|
156
|
+
userAgent: defaultUserAgent
|
|
157
|
+
};
|
|
158
|
+
expect(JSON.stringify(withUserAgent(defaultUserAgent)(DEFAULT_SETTINGS))).toStrictEqual(JSON.stringify(expectedSettings));
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
describe("withUserAgentSuffix", () => {
|
|
162
|
+
it("appends to the original user agent", () => {
|
|
163
|
+
const defaultUserAgent = DEFAULT_SETTINGS.userAgent;
|
|
164
|
+
const addUserAgent = "additional-source/v1.0.0";
|
|
165
|
+
const expectedSettings = {
|
|
166
|
+
...DEFAULT_SETTINGS,
|
|
167
|
+
userAgent: `${defaultUserAgent} ${addUserAgent}`
|
|
168
|
+
};
|
|
169
|
+
expect(JSON.stringify(withUserAgentSuffix(addUserAgent)(DEFAULT_SETTINGS))).toStrictEqual(JSON.stringify(expectedSettings));
|
|
170
|
+
});
|
|
171
|
+
it("replaces the default user agent if the original one is empty", () => {
|
|
172
|
+
const newSettings = {
|
|
173
|
+
...DEFAULT_SETTINGS,
|
|
174
|
+
userAgent: ""
|
|
175
|
+
};
|
|
176
|
+
const addUserAgent = "additional-source/v1.0.0";
|
|
177
|
+
const expectedSettings = {
|
|
178
|
+
...newSettings,
|
|
179
|
+
userAgent: addUserAgent
|
|
180
|
+
};
|
|
181
|
+
expect(JSON.stringify(withUserAgentSuffix(addUserAgent)(newSettings))).toStrictEqual(JSON.stringify(expectedSettings));
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
describe("withAdditionalInterceptors", () => {
|
|
185
|
+
it("appends interceptors to existing ones", () => {
|
|
186
|
+
const oneInterProfile = withAdditionalInterceptors([{ request: ({ request }) => request }])(DEFAULT_SETTINGS);
|
|
187
|
+
const twoInterProfile = withAdditionalInterceptors([{
|
|
188
|
+
response: ({ response }) => response,
|
|
189
|
+
responseError: (err) => err
|
|
190
|
+
}])(oneInterProfile);
|
|
191
|
+
expect(twoInterProfile.interceptors.length).toEqual(2);
|
|
192
|
+
expect(twoInterProfile.interceptors[1].response).toBeDefined();
|
|
193
|
+
expect(twoInterProfile.interceptors[1].responseError).toBeDefined();
|
|
194
|
+
expect(twoInterProfile.interceptors[1].request).toBeUndefined();
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
describe("withLegacyInterceptors", () => {
|
|
198
|
+
it("changes nothing if no legacy interceptor", () => {
|
|
199
|
+
expect(JSON.stringify(withLegacyInterceptors()(DEFAULT_SETTINGS))).toStrictEqual(JSON.stringify(DEFAULT_SETTINGS));
|
|
200
|
+
});
|
|
201
|
+
it("appends the legacy request and response interceptors", () => {
|
|
202
|
+
const legacyInterceptors = (obj) => ({
|
|
203
|
+
...obj,
|
|
204
|
+
requestInterceptors: [({ request }) => request, ({ request }) => request],
|
|
205
|
+
responseInterceptors: [({ response }) => response]
|
|
206
|
+
});
|
|
207
|
+
expect(withLegacyInterceptors()(legacyInterceptors(DEFAULT_SETTINGS)).interceptors.length).toBe(3);
|
|
208
|
+
const legacyReqInterceptors = (obj) => ({
|
|
209
|
+
...obj,
|
|
210
|
+
requestInterceptors: [({ request }) => request, ({ request }) => request]
|
|
211
|
+
});
|
|
212
|
+
expect(withLegacyInterceptors()(legacyReqInterceptors(DEFAULT_SETTINGS)).interceptors.length).toBe(2);
|
|
213
|
+
const legacyResInterceptors = (obj) => ({
|
|
214
|
+
...obj,
|
|
215
|
+
responseInterceptors: [({ response }) => response]
|
|
216
|
+
});
|
|
217
|
+
expect(withLegacyInterceptors()(legacyResInterceptors(DEFAULT_SETTINGS)).interceptors.length).toBe(1);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
//#endregion
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { assertValidAuthenticationSecrets, hasAuthenticationSecrets } from "../client-ini-profile.js";
|
|
2
|
+
import { describe, expect, it, test } from "vitest";
|
|
3
|
+
//#region src/scw/__tests__/client-ini-profile.test.ts
|
|
4
|
+
describe("hasAuthenticationSecrets", () => {
|
|
5
|
+
it("confirms a non-empty payload", () => {
|
|
6
|
+
expect(hasAuthenticationSecrets({
|
|
7
|
+
accessKey: "any-string",
|
|
8
|
+
secretKey: "any-string"
|
|
9
|
+
})).toBeTruthy();
|
|
10
|
+
});
|
|
11
|
+
it("rejects payload with empty access or secret", () => {
|
|
12
|
+
expect(hasAuthenticationSecrets({
|
|
13
|
+
accessKey: "",
|
|
14
|
+
secretKey: ""
|
|
15
|
+
})).toBeFalsy();
|
|
16
|
+
expect(hasAuthenticationSecrets({
|
|
17
|
+
accessKey: "",
|
|
18
|
+
secretKey: "any-string"
|
|
19
|
+
})).toBeFalsy();
|
|
20
|
+
expect(hasAuthenticationSecrets({
|
|
21
|
+
accessKey: "any-string",
|
|
22
|
+
secretKey: ""
|
|
23
|
+
})).toBeFalsy();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
describe("assertValidAuthenticationSecrets", () => {
|
|
27
|
+
it(`doesn't throw for valid secrets`, () => {
|
|
28
|
+
expect(() => {
|
|
29
|
+
assertValidAuthenticationSecrets({
|
|
30
|
+
accessKey: "SCW01234567890123456",
|
|
31
|
+
secretKey: "e4b83996-4c60-449a-98d2-38f5de7b4e6b"
|
|
32
|
+
});
|
|
33
|
+
}).not.toThrow();
|
|
34
|
+
});
|
|
35
|
+
test.each([
|
|
36
|
+
{
|
|
37
|
+
accessKey: "",
|
|
38
|
+
secretKey: ""
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
accessKey: "SCW01234567890123456",
|
|
42
|
+
secretKey: ""
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
accessKey: "",
|
|
46
|
+
secretKey: "e4b83996-4c60-449a-98d2-38f5de7b4e6b"
|
|
47
|
+
}
|
|
48
|
+
])("%s throws for empty keys", (value) => {
|
|
49
|
+
expect(() => {
|
|
50
|
+
assertValidAuthenticationSecrets(value);
|
|
51
|
+
}).toThrow(/* @__PURE__ */ new Error(`Invalid secrets, accessKey & secretKey must be defined. See https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys/`));
|
|
52
|
+
});
|
|
53
|
+
it(`throws for invalid accessKey`, () => {
|
|
54
|
+
expect(() => {
|
|
55
|
+
assertValidAuthenticationSecrets({
|
|
56
|
+
accessKey: "SCW0123",
|
|
57
|
+
secretKey: "e4b83996-4c60-449a-98d2-38f5de7b4e6b"
|
|
58
|
+
});
|
|
59
|
+
}).toThrow(/* @__PURE__ */ new Error(`Invalid access key format 'SCW0123', expected SCWXXXXXXXXXXXXXXXXX format. See https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys/`));
|
|
60
|
+
});
|
|
61
|
+
it(`throws for invalid secretKey`, () => {
|
|
62
|
+
expect(() => {
|
|
63
|
+
assertValidAuthenticationSecrets({
|
|
64
|
+
accessKey: "SCW01234567890123456",
|
|
65
|
+
secretKey: "e4b83996-4c60-449a-98d2"
|
|
66
|
+
});
|
|
67
|
+
}).toThrow(/* @__PURE__ */ new Error(`Invalid secret key format 'e4b83996-4c60-449a-98d2', expected a UUID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. See https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys/`));
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//#endregion
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { assertValidSettings } from "../client-settings.js";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
//#region src/scw/__tests__/client-settings.test.ts
|
|
4
|
+
var VALID_SETTINGS = {
|
|
5
|
+
apiURL: "https://api.scaleway.com",
|
|
6
|
+
defaultOrganizationId: "1f1ca70e-a7d4-4061-8495-09e17e69a0dc",
|
|
7
|
+
defaultPageSize: 10,
|
|
8
|
+
defaultProjectId: "ca69720b-8a3d-4295-8f6d-87257f0474b4",
|
|
9
|
+
defaultRegion: "fr-par",
|
|
10
|
+
defaultZone: "fr-par-1",
|
|
11
|
+
httpClient: fetch,
|
|
12
|
+
interceptors: [],
|
|
13
|
+
requestInterceptors: [],
|
|
14
|
+
responseInterceptors: [],
|
|
15
|
+
userAgent: "scaleway-sdk-js/v1.0.0-beta"
|
|
16
|
+
};
|
|
17
|
+
var INVALID_SETTINGS_LIST = [
|
|
18
|
+
{ apiURL: "https://api.scaleway.com/" },
|
|
19
|
+
{ apiURL: "ftp://api.scaleway.com" },
|
|
20
|
+
{ defaultZone: "fr-par-0" },
|
|
21
|
+
{ defaultZone: "fr-par" },
|
|
22
|
+
{ defaultRegion: "fr-par-1" },
|
|
23
|
+
{ httpClient: "str-client" },
|
|
24
|
+
{ defaultOrganizationId: "" },
|
|
25
|
+
{ defaultOrganizationId: "not-a-uuid-v4" },
|
|
26
|
+
{ defaultProjectId: "" },
|
|
27
|
+
{ defaultProjectId: "not-a-uuid-v4" },
|
|
28
|
+
{ defaultPageSize: 0 },
|
|
29
|
+
{ defaultPageSize: -1 },
|
|
30
|
+
{ defaultPageSize: "42" },
|
|
31
|
+
{ userAgent: null }
|
|
32
|
+
];
|
|
33
|
+
describe("assertValidSettings", () => {
|
|
34
|
+
it("accepts valid Settings object", () => {
|
|
35
|
+
expect(() => {
|
|
36
|
+
assertValidSettings(VALID_SETTINGS);
|
|
37
|
+
}).not.toThrow();
|
|
38
|
+
});
|
|
39
|
+
for (const obj of INVALID_SETTINGS_LIST) {
|
|
40
|
+
const newProfile = {
|
|
41
|
+
...VALID_SETTINGS,
|
|
42
|
+
...obj
|
|
43
|
+
};
|
|
44
|
+
it(`rejects invalid Settings object ${JSON.stringify(obj)}`, () => {
|
|
45
|
+
expect(() => {
|
|
46
|
+
assertValidSettings(newProfile);
|
|
47
|
+
}).toThrow();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
//#endregion
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { withProfile } from "../client-ini-factory.js";
|
|
2
|
+
import { createAdvancedClient, createClient } from "../client.js";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
//#region src/scw/__tests__/client.test.ts
|
|
5
|
+
var withApiURL = (apiURL) => (obj) => ({
|
|
6
|
+
...obj,
|
|
7
|
+
apiURL
|
|
8
|
+
});
|
|
9
|
+
var withPassthroughFetch = (res) => (obj) => ({
|
|
10
|
+
...obj,
|
|
11
|
+
httpClient: () => Promise.resolve(new Response(res))
|
|
12
|
+
});
|
|
13
|
+
describe("createAdvancedClient", () => {
|
|
14
|
+
it("initializes without throwing", () => {
|
|
15
|
+
expect(() => {
|
|
16
|
+
createAdvancedClient();
|
|
17
|
+
}).not.toThrow();
|
|
18
|
+
});
|
|
19
|
+
it("contains override from withProfile", () => {
|
|
20
|
+
expect(createAdvancedClient(withProfile({ defaultRegion: "nl-ams" })).settings.defaultRegion).toBe("nl-ams");
|
|
21
|
+
});
|
|
22
|
+
it("contains override from withProfile with authentication", () => {
|
|
23
|
+
expect(createAdvancedClient(withProfile({
|
|
24
|
+
accessKey: "SCW1234567890ABCDEFG",
|
|
25
|
+
secretKey: "3aef5281-13eb-4705-b858-eb64dd5da24c"
|
|
26
|
+
})).settings.interceptors.length).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
it("does not mutate default requestInterceptors", () => {
|
|
29
|
+
let client = createAdvancedClient(withProfile({
|
|
30
|
+
accessKey: "SCW1234567890ABCDEFG",
|
|
31
|
+
secretKey: "3aef5281-13eb-4705-b858-eb64dd5da24c"
|
|
32
|
+
}));
|
|
33
|
+
client = createAdvancedClient(withProfile({
|
|
34
|
+
accessKey: "SCW1234567890ABCDEFG",
|
|
35
|
+
secretKey: "3aef5281-13eb-4705-b858-eb64dd5da24c"
|
|
36
|
+
}));
|
|
37
|
+
expect(client.settings.interceptors.length).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
it("contains override from custom option", () => {
|
|
40
|
+
const betaApiRoot = "https://api-beta.scaleway.com";
|
|
41
|
+
expect(createAdvancedClient(withApiURL(betaApiRoot)).settings.apiURL).toBe(betaApiRoot);
|
|
42
|
+
});
|
|
43
|
+
it("contains override of httpClient", () => {
|
|
44
|
+
return expect(createAdvancedClient(withPassthroughFetch("hello world")).settings.httpClient("any-url").then((obj) => obj.text())).resolves.toBe("hello world");
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("createClient", () => {
|
|
48
|
+
it("initializes without throwing", () => {
|
|
49
|
+
expect(() => {
|
|
50
|
+
createClient();
|
|
51
|
+
}).not.toThrow();
|
|
52
|
+
});
|
|
53
|
+
it("contains proper default values", () => {
|
|
54
|
+
const client = createAdvancedClient();
|
|
55
|
+
expect(client.settings.apiURL).toBe("https://api.scaleway.com");
|
|
56
|
+
expect(client.settings.userAgent.startsWith("scaleway-sdk-js/")).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
//#endregion
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Decimal } from "../custom-types.js";
|
|
2
|
+
import { marshalMoney, marshalScwFile, marshalTimeSeries, marshalTimeSeriesPoint, unmarshalDecimal, unmarshalMoney, unmarshalScwFile, unmarshalServiceInfo, unmarshalTimeSeries, unmarshalTimeSeriesPoint } from "../custom-marshalling.js";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
//#region src/scw/__tests__/custom-marshalling.test.ts
|
|
5
|
+
describe("unmarshalMoney", () => {
|
|
6
|
+
it("returns the proper object", () => {
|
|
7
|
+
expect(unmarshalMoney({
|
|
8
|
+
currency_code: "EUR",
|
|
9
|
+
nanos: 0,
|
|
10
|
+
units: 42
|
|
11
|
+
})).toStrictEqual({
|
|
12
|
+
currencyCode: "EUR",
|
|
13
|
+
nanos: 0,
|
|
14
|
+
units: 42
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
it("throws for invalid input", () => {
|
|
18
|
+
expect(() => {
|
|
19
|
+
unmarshalMoney(null);
|
|
20
|
+
}).toThrow();
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("unmarshalServiceInfo", () => {
|
|
24
|
+
it("returns the proper object", () => {
|
|
25
|
+
expect(unmarshalServiceInfo({
|
|
26
|
+
description: "Service description",
|
|
27
|
+
documentation_url: "https://",
|
|
28
|
+
name: "TheService",
|
|
29
|
+
version: "v1"
|
|
30
|
+
})).toStrictEqual({
|
|
31
|
+
description: "Service description",
|
|
32
|
+
documentationUrl: "https://",
|
|
33
|
+
name: "TheService",
|
|
34
|
+
version: "v1"
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
it("throws for invalid input", () => {
|
|
38
|
+
expect(() => {
|
|
39
|
+
unmarshalServiceInfo(null);
|
|
40
|
+
}).toThrow();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("unmarshalScwFile", () => {
|
|
44
|
+
it("returns the proper object", () => {
|
|
45
|
+
expect(unmarshalScwFile({
|
|
46
|
+
content: "eyJoZWxsbyI6IndvcmxkIn0=",
|
|
47
|
+
content_type: "text/plain",
|
|
48
|
+
name: "filename"
|
|
49
|
+
})).toStrictEqual({
|
|
50
|
+
content: "eyJoZWxsbyI6IndvcmxkIn0=",
|
|
51
|
+
contentType: "text/plain",
|
|
52
|
+
name: "filename"
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
it("throws for invalid input", () => {
|
|
56
|
+
expect(() => {
|
|
57
|
+
unmarshalScwFile(null);
|
|
58
|
+
}).toThrow();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
describe("unmarshalTimeSeriesPoint", () => {
|
|
62
|
+
it("returns the proper object", () => {
|
|
63
|
+
expect(unmarshalTimeSeriesPoint(["2019-08-08T15:00:00.000Z", 42])).toStrictEqual({
|
|
64
|
+
timestamp: /* @__PURE__ */ new Date("2019-08-08T15:00:00.000Z"),
|
|
65
|
+
value: 42
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
it("throws for invalid input", () => {
|
|
69
|
+
expect(() => {
|
|
70
|
+
unmarshalTimeSeriesPoint(null);
|
|
71
|
+
}).toThrow();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("unmarshalTimeSeries", () => {
|
|
75
|
+
it("returns the proper object", () => {
|
|
76
|
+
expect(unmarshalTimeSeries({
|
|
77
|
+
metadata: { mattress: "cloud" },
|
|
78
|
+
name: "sleep",
|
|
79
|
+
points: [["2019-08-08T15:00:00.000Z", 8]]
|
|
80
|
+
})).toStrictEqual({
|
|
81
|
+
metadata: { mattress: "cloud" },
|
|
82
|
+
name: "sleep",
|
|
83
|
+
points: [{
|
|
84
|
+
timestamp: /* @__PURE__ */ new Date("2019-08-08T15:00:00.000Z"),
|
|
85
|
+
value: 8
|
|
86
|
+
}]
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
it("throws for invalid input", () => {
|
|
90
|
+
expect(() => {
|
|
91
|
+
unmarshalTimeSeries(null);
|
|
92
|
+
}).toThrow();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe("unmarshalDecimal", () => {
|
|
96
|
+
it("returns the proper object", () => {
|
|
97
|
+
const decimal = unmarshalDecimal({ value: "0.01" });
|
|
98
|
+
expect(decimal).toBeInstanceOf(Decimal);
|
|
99
|
+
expect(decimal?.toString()).toStrictEqual("0.01");
|
|
100
|
+
});
|
|
101
|
+
it("throws for invalid input", () => {
|
|
102
|
+
expect(unmarshalDecimal(null)).toBeNull();
|
|
103
|
+
expect(() => {
|
|
104
|
+
unmarshalDecimal({});
|
|
105
|
+
}).toThrow();
|
|
106
|
+
expect(() => {
|
|
107
|
+
unmarshalDecimal({ value: null });
|
|
108
|
+
}).toThrow();
|
|
109
|
+
expect(() => {
|
|
110
|
+
unmarshalDecimal({ value: .02 });
|
|
111
|
+
}).toThrow();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe("marshalScwFile", () => {
|
|
115
|
+
it("returns a the proper object", () => expect(marshalScwFile({
|
|
116
|
+
content: "eyJoZWxsbyI6IndvcmxkIn0=",
|
|
117
|
+
contentType: "text/plain",
|
|
118
|
+
name: "filename"
|
|
119
|
+
})).toStrictEqual({
|
|
120
|
+
content: "eyJoZWxsbyI6IndvcmxkIn0=",
|
|
121
|
+
content_type: "text/plain",
|
|
122
|
+
name: "filename"
|
|
123
|
+
}));
|
|
124
|
+
});
|
|
125
|
+
describe("marshalMoney", () => {
|
|
126
|
+
it("returns the proper object", () => {
|
|
127
|
+
expect(marshalMoney({
|
|
128
|
+
currencyCode: "EUR",
|
|
129
|
+
nanos: 0,
|
|
130
|
+
units: 42
|
|
131
|
+
})).toStrictEqual({
|
|
132
|
+
currency_code: "EUR",
|
|
133
|
+
nanos: 0,
|
|
134
|
+
units: 42
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe("marshalTimeSeriesPoint", () => {
|
|
139
|
+
it("returns the proper object", () => {
|
|
140
|
+
expect(marshalTimeSeriesPoint({
|
|
141
|
+
timestamp: /* @__PURE__ */ new Date("2019-08-08T15:00:00.000Z"),
|
|
142
|
+
value: 42
|
|
143
|
+
})).toStrictEqual({
|
|
144
|
+
timestamp: "2019-08-08T15:00:00.000Z",
|
|
145
|
+
value: 42
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
describe("marshalTimeSeries", () => {
|
|
150
|
+
it("returns the proper object", () => {
|
|
151
|
+
expect(marshalTimeSeries({
|
|
152
|
+
metadata: { mattress: "cloud" },
|
|
153
|
+
name: "sleep",
|
|
154
|
+
points: [{
|
|
155
|
+
timestamp: /* @__PURE__ */ new Date("2019-08-08T15:00:00.000Z"),
|
|
156
|
+
value: 8
|
|
157
|
+
}]
|
|
158
|
+
})).toStrictEqual({
|
|
159
|
+
metadata: { mattress: "cloud" },
|
|
160
|
+
name: "sleep",
|
|
161
|
+
points: [{
|
|
162
|
+
timestamp: "2019-08-08T15:00:00.000Z",
|
|
163
|
+
value: 8
|
|
164
|
+
}]
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
//#endregion
|