@scaleway/sdk-client 2.2.1 → 2.2.2
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.js +10 -4
- 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 +1 -1
- 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 +48 -0
- package/dist/internal/interceptors/__tests__/helpers.test.js +27 -0
- package/dist/internal/interceptors/composer.js +5 -3
- 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 +2 -0
- package/dist/internal/logger/index.js +5 -3
- 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 +2 -1
- package/dist/scw/__tests__/api.test.js +19 -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.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.js +17 -15
- package/dist/scw/custom-types.js +2 -0
- 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.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 -0
- 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.js +6 -4
- 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 +1 -1
|
@@ -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
|
package/dist/scw/api.js
CHANGED
package/dist/scw/auth.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { addAsyncHeaderInterceptor, addHeaderInterceptor } from "../internal/interceptors/helpers.js";
|
|
2
2
|
import { assertValidAuthenticationSecrets } from "./client-ini-profile.js";
|
|
3
3
|
import { AUTH_HEADER_KEY, SESSION_HEADER_KEY } from "./constants.js";
|
|
4
|
+
//#region src/scw/auth.ts
|
|
4
5
|
/**
|
|
5
6
|
* Authentication utilities for Scaleway SDK.
|
|
6
7
|
*
|
|
@@ -9,6 +10,15 @@ import { AUTH_HEADER_KEY, SESSION_HEADER_KEY } from "./constants.js";
|
|
|
9
10
|
* tooltips and generated documentation.
|
|
10
11
|
*/
|
|
11
12
|
/**
|
|
13
|
+
* Add an JWT Session Header to a request through an interceptor.
|
|
14
|
+
*
|
|
15
|
+
* @param request - The request to modify
|
|
16
|
+
* @param getJwt - The session value
|
|
17
|
+
* @returns The Request interceptor
|
|
18
|
+
*
|
|
19
|
+
*/
|
|
20
|
+
var addSessionHeader = async ({ request, getAsyncToken }) => addAsyncHeaderInterceptor(SESSION_HEADER_KEY, getAsyncToken)({ request });
|
|
21
|
+
/**
|
|
12
22
|
* Authenticates with a session token.
|
|
13
23
|
*
|
|
14
24
|
* @param getToken - The token accessor
|
|
@@ -18,7 +28,7 @@ import { AUTH_HEADER_KEY, SESSION_HEADER_KEY } from "./constants.js";
|
|
|
18
28
|
*
|
|
19
29
|
* @internal
|
|
20
30
|
*/
|
|
21
|
-
|
|
31
|
+
var authenticateWithSessionToken = (getToken) => addAsyncHeaderInterceptor(SESSION_HEADER_KEY, getToken);
|
|
22
32
|
/**
|
|
23
33
|
* Authenticates with a secrets.
|
|
24
34
|
*
|
|
@@ -30,7 +40,7 @@ const authenticateWithSessionToken = (getToken) => addAsyncHeaderInterceptor(SES
|
|
|
30
40
|
*
|
|
31
41
|
* @internal
|
|
32
42
|
*/
|
|
33
|
-
|
|
43
|
+
var authenticateWithSecrets = (secrets) => {
|
|
34
44
|
assertValidAuthenticationSecrets(secrets);
|
|
35
45
|
return addHeaderInterceptor(AUTH_HEADER_KEY, secrets.secretKey);
|
|
36
46
|
};
|
|
@@ -42,7 +52,7 @@ const authenticateWithSecrets = (secrets) => {
|
|
|
42
52
|
*
|
|
43
53
|
* @internal
|
|
44
54
|
*/
|
|
45
|
-
|
|
55
|
+
var obfuscateToken = (key) => `${key.substring(0, 5)}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`;
|
|
46
56
|
/**
|
|
47
57
|
* Obfuscates an UUID.
|
|
48
58
|
*
|
|
@@ -51,7 +61,7 @@ const obfuscateToken = (key) => `${key.substring(0, 5)}xxxxxxxxxxxxxxxxxxxxxxxxx
|
|
|
51
61
|
*
|
|
52
62
|
* @internal
|
|
53
63
|
*/
|
|
54
|
-
|
|
64
|
+
var obfuscateUUID = (key) => `${key.substring(0, 8)}-xxxx-xxxx-xxxx-xxxxxxxxxxxx`;
|
|
55
65
|
/**
|
|
56
66
|
* Obfuscates headers entry.
|
|
57
67
|
*
|
|
@@ -60,9 +70,10 @@ const obfuscateUUID = (key) => `${key.substring(0, 8)}-xxxx-xxxx-xxxx-xxxxxxxxxx
|
|
|
60
70
|
*
|
|
61
71
|
* @internal
|
|
62
72
|
*/
|
|
63
|
-
|
|
73
|
+
var obfuscateAuthHeadersEntry = ([name, value]) => {
|
|
64
74
|
if (name === "x-session-token") return [name, obfuscateToken(value)];
|
|
65
75
|
if (name === "x-auth-token") return [name, obfuscateUUID(value)];
|
|
66
76
|
return [name, value];
|
|
67
77
|
};
|
|
68
|
-
|
|
78
|
+
//#endregion
|
|
79
|
+
export { addSessionHeader, authenticateWithSecrets, authenticateWithSessionToken, obfuscateAuthHeadersEntry, obfuscateToken, obfuscateUUID };
|