Skip to content

Commit a31e36d

Browse files
authored
feat: allowed signature to be passed in kv-store/datasets (#761)
When working with output schema in UI I found that we do not allow passing signature in client to read data from storages. This PR does not fully resolve it, but it resolves most of the issues I found. I will leave the rest to Daniil, since he might now better which endpoints use signatures and which do not.
1 parent 5efd68a commit a31e36d

File tree

4 files changed

+99
-1
lines changed

4 files changed

+99
-1
lines changed

src/resource_clients/dataset.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class DatasetClient<
6969
skipHidden: ow.optional.boolean,
7070
unwind: ow.optional.any(ow.string, ow.array.ofType(ow.string)),
7171
view: ow.optional.string,
72+
signature: ow.optional.string,
7273
}),
7374
);
7475

@@ -109,6 +110,7 @@ export class DatasetClient<
109110
view: ow.optional.string,
110111
xmlRoot: ow.optional.string,
111112
xmlRow: ow.optional.string,
113+
signature: ow.optional.string,
112114
}),
113115
);
114116

@@ -272,6 +274,7 @@ export interface DatasetClientListItemOptions {
272274
skipHidden?: boolean;
273275
unwind?: string | string[]; // TODO: when doing a breaking change release, change to string[] only
274276
view?: string;
277+
signature?: string;
275278
}
276279

277280
export interface DatasetClientCreateItemsUrlOptions extends DatasetClientListItemOptions {

src/resource_clients/key_value_store.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export class KeyValueStoreClient extends ResourceClient {
7272
exclusiveStartKey: ow.optional.string,
7373
collection: ow.optional.string,
7474
prefix: ow.optional.string,
75+
signature: ow.optional.string,
7576
}),
7677
);
7778

@@ -201,6 +202,7 @@ export class KeyValueStoreClient extends ResourceClient {
201202
buffer: ow.optional.boolean,
202203
stream: ow.optional.boolean,
203204
disableRedirect: ow.optional.boolean,
205+
signature: ow.optional.string,
204206
}),
205207
);
206208

@@ -215,10 +217,13 @@ export class KeyValueStoreClient extends ResourceClient {
215217
);
216218
}
217219

220+
const queryParams: Pick<KeyValueClientGetRecordOptions, 'signature'> = {};
221+
if (options.signature) queryParams.signature = options.signature;
222+
218223
const requestOpts: Record<string, unknown> = {
219224
url: this._url(`records/${key}`),
220225
method: 'GET',
221-
params: this._params(),
226+
params: this._params(queryParams),
222227
timeout: DEFAULT_TIMEOUT_MILLIS,
223228
};
224229

@@ -353,6 +358,7 @@ export interface KeyValueClientListKeysOptions {
353358
exclusiveStartKey?: string;
354359
collection?: string;
355360
prefix?: string;
361+
signature?: string;
356362
}
357363

358364
export interface KeyValueClientCreateKeysUrlOptions extends KeyValueClientListKeysOptions {
@@ -377,6 +383,7 @@ export interface KeyValueListItem {
377383
export interface KeyValueClientGetRecordOptions {
378384
buffer?: boolean;
379385
stream?: boolean;
386+
signature?: string;
380387
}
381388

382389
export interface KeyValueStoreRecord<T> {

test/datasets.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,28 @@ describe('Dataset methods', () => {
224224
validateRequest(qs, { datasetId });
225225
});
226226

227+
test('listItems() correctly passes signature', async () => {
228+
const datasetId = 'some-id';
229+
const body = [{ test: 'value' }];
230+
const headers = {
231+
'content-type': 'application/json; chartset=utf-8',
232+
'x-apify-pagination-total': '1',
233+
'x-apify-pagination-offset': '1',
234+
'x-apify-pagination-count': '0',
235+
'x-apify-pagination-limit': '1',
236+
// TODO: https://github.com/apify/apify-core/issues/3503
237+
'x-apify-pagination-desc': false,
238+
};
239+
const qs = { limit: 1, offset: 1, signature: 'some-signature' };
240+
mockServer.setResponse({ body, headers });
241+
242+
await client.dataset(datasetId).listItems(qs);
243+
validateRequest(qs, { datasetId });
244+
245+
await page.evaluate((id, opts) => client.dataset(id).listItems(opts), datasetId, qs);
246+
validateRequest(qs, { datasetId });
247+
});
248+
227249
test("downloadItems() doesn't parse application/json", async () => {
228250
const datasetId = 'some-id';
229251
const body = JSON.stringify({ a: 'foo', b: ['bar1', 'bar2'] });
@@ -258,6 +280,36 @@ describe('Dataset methods', () => {
258280
validateRequest({ format, ...options }, { datasetId });
259281
});
260282

283+
test('downloadItems() correctly passes signature', async () => {
284+
const datasetId = 'some-id';
285+
const body = JSON.stringify({ a: 'foo', b: ['bar1', 'bar2'] });
286+
const format = 'json';
287+
const options = {
288+
bom: true,
289+
signature: 'some-signature',
290+
};
291+
const headers = {
292+
contentType: 'application/json; charset=utf-8',
293+
};
294+
295+
mockServer.setResponse({ body, headers });
296+
297+
await client.dataset(datasetId).downloadItems(format, options);
298+
validateRequest({ format, ...options }, { datasetId });
299+
300+
await page.evaluate(
301+
async (id, f, opts) => {
302+
const res = await client.dataset(id).downloadItems(f, opts);
303+
const decoder = new TextDecoder();
304+
return decoder.decode(res);
305+
},
306+
datasetId,
307+
format,
308+
options,
309+
);
310+
validateRequest({ format, ...options }, { datasetId });
311+
});
312+
261313
test('pushItems() works with object', async () => {
262314
const datasetId = '201';
263315
const data = { someData: 'someValue' };

test/key_value_stores.test.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,24 @@ describe('Key-Value Store methods', () => {
146146
validateRequest(query, { storeId });
147147
});
148148

149+
test('listKeys() passes signature', async () => {
150+
const storeId = 'some-id';
151+
152+
const query = {
153+
limit: 10,
154+
exclusiveStartKey: 'fromKey',
155+
collection: 'my-collection',
156+
prefix: 'my-prefix',
157+
signature: 'some-signature',
158+
};
159+
160+
await client.keyValueStore(storeId).listKeys(query);
161+
validateRequest(query, { storeId });
162+
163+
await page.evaluate((id, opts) => client.keyValueStore(id).listKeys(opts), storeId, query);
164+
validateRequest(query, { storeId });
165+
});
166+
149167
test('recordExists() works', async () => {
150168
const key = 'some-key';
151169
const storeId = 'some-id';
@@ -367,6 +385,24 @@ describe('Key-Value Store methods', () => {
367385
}
368386
});
369387

388+
test('getRecord() correctly passes signature', async () => {
389+
const key = 'some-key';
390+
const storeId = 'some-id';
391+
const options = {
392+
signature: 'some-signature',
393+
};
394+
395+
const body = { foo: 'bar', baz: [1, 2] };
396+
const expectedContentType = 'application/json; charset=utf-8';
397+
const expectedHeaders = {
398+
'content-type': expectedContentType,
399+
};
400+
401+
mockServer.setResponse({ headers: expectedHeaders, body });
402+
await client.keyValueStore(storeId).getRecord(key, options);
403+
validateRequest(options, { storeId, key });
404+
});
405+
370406
test('getRecord() returns undefined on 404 status code (RECORD_NOT_FOUND)', async () => {
371407
const key = 'some-key';
372408
const storeId = 'some-id';

0 commit comments

Comments
 (0)