@yolk-sdk/emulators 0.1.0-canary.96
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/LICENSE +21 -0
- package/README.md +2135 -0
- package/dist/anthropic.d.mts +269 -0
- package/dist/anthropic.d.mts.map +1 -0
- package/dist/anthropic.mjs +177 -0
- package/dist/anthropic.mjs.map +1 -0
- package/dist/chat-completions.d.mts +288 -0
- package/dist/chat-completions.d.mts.map +1 -0
- package/dist/chat-completions.mjs +451 -0
- package/dist/chat-completions.mjs.map +1 -0
- package/dist/codex.d.mts +265 -0
- package/dist/codex.d.mts.map +1 -0
- package/dist/codex.mjs +199 -0
- package/dist/codex.mjs.map +1 -0
- package/dist/dropbox/api.d.mts +63 -0
- package/dist/dropbox/api.d.mts.map +1 -0
- package/dist/dropbox/api.mjs +565 -0
- package/dist/dropbox/api.mjs.map +1 -0
- package/dist/dropbox/state.d.mts +136 -0
- package/dist/dropbox/state.d.mts.map +1 -0
- package/dist/dropbox/state.mjs +209 -0
- package/dist/dropbox/state.mjs.map +1 -0
- package/dist/dropbox.d.mts +79 -0
- package/dist/dropbox.d.mts.map +1 -0
- package/dist/dropbox.mjs +124 -0
- package/dist/dropbox.mjs.map +1 -0
- package/dist/email-fixtures.d.mts +36 -0
- package/dist/email-fixtures.d.mts.map +1 -0
- package/dist/email-fixtures.mjs +1080 -0
- package/dist/email-fixtures.mjs.map +1 -0
- package/dist/email.d.mts +160 -0
- package/dist/email.d.mts.map +1 -0
- package/dist/email.mjs +608 -0
- package/dist/email.mjs.map +1 -0
- package/dist/emulator-compose.d.mts +40 -0
- package/dist/emulator-compose.d.mts.map +1 -0
- package/dist/emulator-compose.mjs +91 -0
- package/dist/emulator-compose.mjs.map +1 -0
- package/dist/emulator-http.d.mts +53 -0
- package/dist/emulator-http.d.mts.map +1 -0
- package/dist/emulator-http.mjs +117 -0
- package/dist/emulator-http.mjs.map +1 -0
- package/dist/emulator-kernel.d.mts +176 -0
- package/dist/emulator-kernel.d.mts.map +1 -0
- package/dist/emulator-kernel.mjs +413 -0
- package/dist/emulator-kernel.mjs.map +1 -0
- package/dist/fixture-route.d.mts +129 -0
- package/dist/fixture-route.d.mts.map +1 -0
- package/dist/fixture-route.mjs +257 -0
- package/dist/fixture-route.mjs.map +1 -0
- package/dist/fortnox/api.d.mts +92 -0
- package/dist/fortnox/api.d.mts.map +1 -0
- package/dist/fortnox/api.mjs +750 -0
- package/dist/fortnox/api.mjs.map +1 -0
- package/dist/fortnox/state.d.mts +533 -0
- package/dist/fortnox/state.d.mts.map +1 -0
- package/dist/fortnox/state.mjs +612 -0
- package/dist/fortnox/state.mjs.map +1 -0
- package/dist/fortnox.d.mts +128 -0
- package/dist/fortnox.d.mts.map +1 -0
- package/dist/fortnox.mjs +403 -0
- package/dist/fortnox.mjs.map +1 -0
- package/dist/gateway-evaluate-recordings.d.mts +12 -0
- package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
- package/dist/gateway-evaluate-recordings.mjs +128 -0
- package/dist/gateway-evaluate-recordings.mjs.map +1 -0
- package/dist/gateway.d.mts +272 -0
- package/dist/gateway.d.mts.map +1 -0
- package/dist/gateway.mjs +400 -0
- package/dist/gateway.mjs.map +1 -0
- package/dist/github/api.d.mts +63 -0
- package/dist/github/api.d.mts.map +1 -0
- package/dist/github/api.mjs +527 -0
- package/dist/github/api.mjs.map +1 -0
- package/dist/github/state.d.mts +272 -0
- package/dist/github/state.d.mts.map +1 -0
- package/dist/github/state.mjs +303 -0
- package/dist/github/state.mjs.map +1 -0
- package/dist/github.d.mts +77 -0
- package/dist/github.d.mts.map +1 -0
- package/dist/github.mjs +180 -0
- package/dist/github.mjs.map +1 -0
- package/dist/google/calendar.d.mts +10 -0
- package/dist/google/calendar.d.mts.map +1 -0
- package/dist/google/calendar.mjs +252 -0
- package/dist/google/calendar.mjs.map +1 -0
- package/dist/google/drive.d.mts +8 -0
- package/dist/google/drive.d.mts.map +1 -0
- package/dist/google/drive.mjs +252 -0
- package/dist/google/drive.mjs.map +1 -0
- package/dist/google/gmail.d.mts +10 -0
- package/dist/google/gmail.d.mts.map +1 -0
- package/dist/google/gmail.mjs +591 -0
- package/dist/google/gmail.mjs.map +1 -0
- package/dist/google/shared.d.mts +107 -0
- package/dist/google/shared.d.mts.map +1 -0
- package/dist/google/shared.mjs +146 -0
- package/dist/google/shared.mjs.map +1 -0
- package/dist/google/state.d.mts +385 -0
- package/dist/google/state.d.mts.map +1 -0
- package/dist/google/state.mjs +496 -0
- package/dist/google/state.mjs.map +1 -0
- package/dist/google.d.mts +75 -0
- package/dist/google.d.mts.map +1 -0
- package/dist/google.mjs +210 -0
- package/dist/google.mjs.map +1 -0
- package/dist/linkedin-search/api.d.mts +45 -0
- package/dist/linkedin-search/api.d.mts.map +1 -0
- package/dist/linkedin-search/api.mjs +181 -0
- package/dist/linkedin-search/api.mjs.map +1 -0
- package/dist/linkedin-search/state.d.mts +123 -0
- package/dist/linkedin-search/state.d.mts.map +1 -0
- package/dist/linkedin-search/state.mjs +261 -0
- package/dist/linkedin-search/state.mjs.map +1 -0
- package/dist/linkedin-search.d.mts +69 -0
- package/dist/linkedin-search.d.mts.map +1 -0
- package/dist/linkedin-search.mjs +158 -0
- package/dist/linkedin-search.mjs.map +1 -0
- package/dist/mcp/api.d.mts +44 -0
- package/dist/mcp/api.d.mts.map +1 -0
- package/dist/mcp/api.mjs +557 -0
- package/dist/mcp/api.mjs.map +1 -0
- package/dist/mcp/recordings.d.mts +40 -0
- package/dist/mcp/recordings.d.mts.map +1 -0
- package/dist/mcp/recordings.mjs +2390 -0
- package/dist/mcp/recordings.mjs.map +1 -0
- package/dist/mcp/state.d.mts +71 -0
- package/dist/mcp/state.d.mts.map +1 -0
- package/dist/mcp/state.mjs +83 -0
- package/dist/mcp/state.mjs.map +1 -0
- package/dist/mcp.d.mts +84 -0
- package/dist/mcp.d.mts.map +1 -0
- package/dist/mcp.mjs +241 -0
- package/dist/mcp.mjs.map +1 -0
- package/dist/messages.d.mts +183 -0
- package/dist/messages.d.mts.map +1 -0
- package/dist/messages.mjs +532 -0
- package/dist/messages.mjs.map +1 -0
- package/dist/microsoft/api.d.mts +47 -0
- package/dist/microsoft/api.d.mts.map +1 -0
- package/dist/microsoft/api.mjs +178 -0
- package/dist/microsoft/api.mjs.map +1 -0
- package/dist/microsoft/calendar.d.mts +30 -0
- package/dist/microsoft/calendar.d.mts.map +1 -0
- package/dist/microsoft/calendar.mjs +271 -0
- package/dist/microsoft/calendar.mjs.map +1 -0
- package/dist/microsoft/drive.d.mts +36 -0
- package/dist/microsoft/drive.d.mts.map +1 -0
- package/dist/microsoft/drive.mjs +298 -0
- package/dist/microsoft/drive.mjs.map +1 -0
- package/dist/microsoft/graph.d.mts +198 -0
- package/dist/microsoft/graph.d.mts.map +1 -0
- package/dist/microsoft/graph.mjs +264 -0
- package/dist/microsoft/graph.mjs.map +1 -0
- package/dist/microsoft/mail.d.mts +48 -0
- package/dist/microsoft/mail.d.mts.map +1 -0
- package/dist/microsoft/mail.mjs +488 -0
- package/dist/microsoft/mail.mjs.map +1 -0
- package/dist/microsoft/state.d.mts +480 -0
- package/dist/microsoft/state.d.mts.map +1 -0
- package/dist/microsoft/state.mjs +625 -0
- package/dist/microsoft/state.mjs.map +1 -0
- package/dist/microsoft.d.mts +168 -0
- package/dist/microsoft.d.mts.map +1 -0
- package/dist/microsoft.mjs +483 -0
- package/dist/microsoft.mjs.map +1 -0
- package/dist/node.d.mts +47 -0
- package/dist/node.d.mts.map +1 -0
- package/dist/node.mjs +178 -0
- package/dist/node.mjs.map +1 -0
- package/dist/notion/api.d.mts +51 -0
- package/dist/notion/api.d.mts.map +1 -0
- package/dist/notion/api.mjs +507 -0
- package/dist/notion/api.mjs.map +1 -0
- package/dist/notion/state.d.mts +329 -0
- package/dist/notion/state.d.mts.map +1 -0
- package/dist/notion/state.mjs +432 -0
- package/dist/notion/state.mjs.map +1 -0
- package/dist/notion.d.mts +72 -0
- package/dist/notion.d.mts.map +1 -0
- package/dist/notion.mjs +127 -0
- package/dist/notion.mjs.map +1 -0
- package/dist/openai.d.mts +165 -0
- package/dist/openai.d.mts.map +1 -0
- package/dist/openai.mjs +141 -0
- package/dist/openai.mjs.map +1 -0
- package/dist/opencode-recordings.d.mts +7 -0
- package/dist/opencode-recordings.d.mts.map +1 -0
- package/dist/opencode-recordings.mjs +222 -0
- package/dist/opencode-recordings.mjs.map +1 -0
- package/dist/opencode.d.mts +70 -0
- package/dist/opencode.d.mts.map +1 -0
- package/dist/opencode.mjs +217 -0
- package/dist/opencode.mjs.map +1 -0
- package/dist/r2-fixtures.d.mts +36 -0
- package/dist/r2-fixtures.d.mts.map +1 -0
- package/dist/r2-fixtures.mjs +245 -0
- package/dist/r2-fixtures.mjs.map +1 -0
- package/dist/r2-guard.d.mts +43 -0
- package/dist/r2-guard.d.mts.map +1 -0
- package/dist/r2-guard.mjs +215 -0
- package/dist/r2-guard.mjs.map +1 -0
- package/dist/r2.d.mts +166 -0
- package/dist/r2.d.mts.map +1 -0
- package/dist/r2.mjs +517 -0
- package/dist/r2.mjs.map +1 -0
- package/dist/responses.d.mts +231 -0
- package/dist/responses.d.mts.map +1 -0
- package/dist/responses.mjs +556 -0
- package/dist/responses.mjs.map +1 -0
- package/dist/route-evidence.d.mts +45 -0
- package/dist/route-evidence.d.mts.map +1 -0
- package/dist/route-evidence.mjs +56 -0
- package/dist/route-evidence.mjs.map +1 -0
- package/dist/router.d.mts +93 -0
- package/dist/router.d.mts.map +1 -0
- package/dist/router.mjs +259 -0
- package/dist/router.mjs.map +1 -0
- package/dist/stateful-core.d.mts +17 -0
- package/dist/stateful-core.d.mts.map +1 -0
- package/dist/stateful-core.mjs +41 -0
- package/dist/stateful-core.mjs.map +1 -0
- package/dist/stateful-emulator.d.mts +565 -0
- package/dist/stateful-emulator.d.mts.map +1 -0
- package/dist/stateful-emulator.mjs +1228 -0
- package/dist/stateful-emulator.mjs.map +1 -0
- package/dist/stateful-secrets.d.mts +84 -0
- package/dist/stateful-secrets.d.mts.map +1 -0
- package/dist/stateful-secrets.mjs +216 -0
- package/dist/stateful-secrets.mjs.map +1 -0
- package/dist/subscription-usage-recordings.d.mts +9 -0
- package/dist/subscription-usage-recordings.d.mts.map +1 -0
- package/dist/subscription-usage-recordings.mjs +53 -0
- package/dist/subscription-usage-recordings.mjs.map +1 -0
- package/dist/subscription-usage.d.mts +53 -0
- package/dist/subscription-usage.d.mts.map +1 -0
- package/dist/subscription-usage.mjs +21 -0
- package/dist/subscription-usage.mjs.map +1 -0
- package/dist/telegram/api.d.mts +34 -0
- package/dist/telegram/api.d.mts.map +1 -0
- package/dist/telegram/api.mjs +257 -0
- package/dist/telegram/api.mjs.map +1 -0
- package/dist/telegram/state.d.mts +128 -0
- package/dist/telegram/state.d.mts.map +1 -0
- package/dist/telegram/state.mjs +154 -0
- package/dist/telegram/state.mjs.map +1 -0
- package/dist/telegram.d.mts +62 -0
- package/dist/telegram.d.mts.map +1 -0
- package/dist/telegram.mjs +127 -0
- package/dist/telegram.mjs.map +1 -0
- package/dist/todoist/api.d.mts +55 -0
- package/dist/todoist/api.d.mts.map +1 -0
- package/dist/todoist/api.mjs +415 -0
- package/dist/todoist/api.mjs.map +1 -0
- package/dist/todoist/state.d.mts +275 -0
- package/dist/todoist/state.d.mts.map +1 -0
- package/dist/todoist/state.mjs +345 -0
- package/dist/todoist/state.mjs.map +1 -0
- package/dist/todoist.d.mts +68 -0
- package/dist/todoist.d.mts.map +1 -0
- package/dist/todoist.mjs +181 -0
- package/dist/todoist.mjs.map +1 -0
- package/dist/xai.d.mts +267 -0
- package/dist/xai.d.mts.map +1 -0
- package/dist/xai.mjs +252 -0
- package/dist/xai.mjs.map +1 -0
- package/package.json +157 -0
- package/src/anthropic.ts +289 -0
- package/src/chat-completions.ts +924 -0
- package/src/codex.ts +296 -0
- package/src/dropbox/api.ts +1084 -0
- package/src/dropbox/state.ts +364 -0
- package/src/dropbox.ts +203 -0
- package/src/email-fixtures.ts +1297 -0
- package/src/email.ts +1108 -0
- package/src/emulator-compose.ts +147 -0
- package/src/emulator-http.ts +184 -0
- package/src/emulator-kernel.ts +844 -0
- package/src/fixture-route.ts +561 -0
- package/src/fortnox/api.ts +1352 -0
- package/src/fortnox/state.ts +798 -0
- package/src/fortnox.ts +801 -0
- package/src/gateway-evaluate-recordings.ts +153 -0
- package/src/gateway.ts +530 -0
- package/src/github/api.ts +986 -0
- package/src/github/state.ts +439 -0
- package/src/github.ts +271 -0
- package/src/google/calendar.ts +486 -0
- package/src/google/drive.ts +471 -0
- package/src/google/gmail.ts +1011 -0
- package/src/google/shared.ts +321 -0
- package/src/google/state.ts +686 -0
- package/src/google.ts +298 -0
- package/src/linkedin-search/api.ts +363 -0
- package/src/linkedin-search/state.ts +373 -0
- package/src/linkedin-search.ts +241 -0
- package/src/mcp/api.ts +995 -0
- package/src/mcp/recordings.ts +2665 -0
- package/src/mcp/state.ts +125 -0
- package/src/mcp.ts +319 -0
- package/src/messages.ts +844 -0
- package/src/microsoft/api.ts +426 -0
- package/src/microsoft/calendar.ts +491 -0
- package/src/microsoft/drive.ts +541 -0
- package/src/microsoft/graph.ts +550 -0
- package/src/microsoft/mail.ts +833 -0
- package/src/microsoft/state.ts +822 -0
- package/src/microsoft.ts +982 -0
- package/src/node.ts +293 -0
- package/src/notion/api.ts +976 -0
- package/src/notion/state.ts +512 -0
- package/src/notion.ts +210 -0
- package/src/openai.ts +198 -0
- package/src/opencode-recordings.ts +257 -0
- package/src/opencode.ts +271 -0
- package/src/r2-fixtures.ts +295 -0
- package/src/r2-guard.ts +323 -0
- package/src/r2.ts +890 -0
- package/src/responses.ts +901 -0
- package/src/route-evidence.ts +90 -0
- package/src/router.ts +490 -0
- package/src/stateful-core.ts +70 -0
- package/src/stateful-emulator.ts +2571 -0
- package/src/stateful-secrets.ts +299 -0
- package/src/subscription-usage-recordings.ts +77 -0
- package/src/subscription-usage.ts +68 -0
- package/src/telegram/api.ts +437 -0
- package/src/telegram/state.ts +229 -0
- package/src/telegram.ts +227 -0
- package/src/todoist/api.ts +736 -0
- package/src/todoist/state.ts +456 -0
- package/src/todoist.ts +316 -0
- package/src/xai.ts +341 -0
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drive routes of the Google emulator (internal): the folder listing with page tokens, file reads
|
|
3
|
+
* with the connector's full `fields` selection, and the lifecycle of a run-scoped folder (create,
|
|
4
|
+
* trash, permanent delete, and the recorded 404 afterwards).
|
|
5
|
+
*
|
|
6
|
+
* Every answer comes from the Drive fixtures, through the seed or the request, or is minted (folder
|
|
7
|
+
* ids, page tokens, and `createdTime` / `modifiedTime` / `trashedTime` from the injectable clock).
|
|
8
|
+
* Every Drive request must send `accept: application/json`, as the fixtures record.
|
|
9
|
+
*
|
|
10
|
+
* @experimental
|
|
11
|
+
*/
|
|
12
|
+
import { Predicate } from 'effect'
|
|
13
|
+
import type * as Schema from 'effect/Schema'
|
|
14
|
+
import {
|
|
15
|
+
exactBodyKeys,
|
|
16
|
+
exactQuery,
|
|
17
|
+
isNotEmulated,
|
|
18
|
+
notEmulated,
|
|
19
|
+
statefulRoute,
|
|
20
|
+
type EmulatedRequest,
|
|
21
|
+
type NotEmulated
|
|
22
|
+
} from '../stateful-emulator.ts'
|
|
23
|
+
import {
|
|
24
|
+
answer,
|
|
25
|
+
evidence,
|
|
26
|
+
googleEmulatorApisOrigin,
|
|
27
|
+
googleErrorBody,
|
|
28
|
+
googleJson,
|
|
29
|
+
googleNoContent,
|
|
30
|
+
isRunText,
|
|
31
|
+
listPage,
|
|
32
|
+
pageSize,
|
|
33
|
+
param,
|
|
34
|
+
recordedValue,
|
|
35
|
+
type GoogleRoute
|
|
36
|
+
} from './shared.ts'
|
|
37
|
+
import {
|
|
38
|
+
driveFile,
|
|
39
|
+
driveFolderMimeType,
|
|
40
|
+
mintedFolderId,
|
|
41
|
+
mintedFolderIdPattern,
|
|
42
|
+
type GoogleEmulatorDriveFile,
|
|
43
|
+
type GoogleEmulatorState
|
|
44
|
+
} from './state.ts'
|
|
45
|
+
|
|
46
|
+
const listCase = 'google.drive.list-page-token'
|
|
47
|
+
|
|
48
|
+
const fieldsCase = 'google.drive.get-file-fields'
|
|
49
|
+
|
|
50
|
+
const folderCase = 'google.drive.folder-trash-delete'
|
|
51
|
+
|
|
52
|
+
const fileIdSegment = /^[A-Za-z0-9_-]{10,200}$/
|
|
53
|
+
|
|
54
|
+
const drive = (
|
|
55
|
+
method: string,
|
|
56
|
+
path: string,
|
|
57
|
+
write: boolean,
|
|
58
|
+
caseIds: ReadonlyArray<string>,
|
|
59
|
+
withFile: boolean
|
|
60
|
+
) =>
|
|
61
|
+
evidence(
|
|
62
|
+
googleEmulatorApisOrigin,
|
|
63
|
+
method,
|
|
64
|
+
`/drive/v3/files${path}`,
|
|
65
|
+
write,
|
|
66
|
+
caseIds,
|
|
67
|
+
withFile ? { fileId: fileIdSegment } : {}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The file `fields` selection the connector sends (a data copy of `googleDriveFileFields`; the
|
|
72
|
+
* fixture URLs carry it, so the drift test fails when it changes).
|
|
73
|
+
*/
|
|
74
|
+
const fileFields = [
|
|
75
|
+
'kind',
|
|
76
|
+
'driveId',
|
|
77
|
+
'id',
|
|
78
|
+
'name',
|
|
79
|
+
'mimeType',
|
|
80
|
+
'description',
|
|
81
|
+
'starred',
|
|
82
|
+
'trashed',
|
|
83
|
+
'explicitlyTrashed',
|
|
84
|
+
'trashedTime',
|
|
85
|
+
'trashingUser',
|
|
86
|
+
'parents',
|
|
87
|
+
'spaces',
|
|
88
|
+
'version',
|
|
89
|
+
'webContentLink',
|
|
90
|
+
'webViewLink',
|
|
91
|
+
'iconLink',
|
|
92
|
+
'hasThumbnail',
|
|
93
|
+
'thumbnailLink',
|
|
94
|
+
'thumbnailVersion',
|
|
95
|
+
'viewedByMe',
|
|
96
|
+
'viewedByMeTime',
|
|
97
|
+
'createdTime',
|
|
98
|
+
'modifiedTime',
|
|
99
|
+
'modifiedByMeTime',
|
|
100
|
+
'sharedWithMeTime',
|
|
101
|
+
'ownedByMe',
|
|
102
|
+
'shared',
|
|
103
|
+
'owners',
|
|
104
|
+
'permissionIds',
|
|
105
|
+
'lastModifyingUser',
|
|
106
|
+
'sharingUser',
|
|
107
|
+
'size',
|
|
108
|
+
'quotaBytesUsed',
|
|
109
|
+
'md5Checksum',
|
|
110
|
+
'sha1Checksum',
|
|
111
|
+
'sha256Checksum',
|
|
112
|
+
'fileExtension',
|
|
113
|
+
'fullFileExtension',
|
|
114
|
+
'originalFilename',
|
|
115
|
+
'headRevisionId',
|
|
116
|
+
'folderColorRgb',
|
|
117
|
+
'resourceKey',
|
|
118
|
+
'copyRequiresWriterPermission',
|
|
119
|
+
'writersCanShare',
|
|
120
|
+
'properties',
|
|
121
|
+
'appProperties',
|
|
122
|
+
'capabilities',
|
|
123
|
+
'shortcutDetails',
|
|
124
|
+
'contentRestrictions',
|
|
125
|
+
'linkShareMetadata'
|
|
126
|
+
].join(',')
|
|
127
|
+
|
|
128
|
+
const listFields = `kind,nextPageToken,incompleteSearch,files(${fileFields})`
|
|
129
|
+
|
|
130
|
+
const parentQueryPattern = /^'([A-Za-z0-9_-]{10,200})' in parents and trashed = false$/
|
|
131
|
+
|
|
132
|
+
// Rendering, in the fixture key order.
|
|
133
|
+
|
|
134
|
+
type FileHead = {
|
|
135
|
+
readonly kind: string
|
|
136
|
+
readonly id: string
|
|
137
|
+
readonly name: string
|
|
138
|
+
readonly mimeType: string
|
|
139
|
+
readonly starred: boolean
|
|
140
|
+
readonly trashed: boolean
|
|
141
|
+
readonly explicitlyTrashed: boolean
|
|
142
|
+
trashedTime?: string
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
type FileListHead = {
|
|
146
|
+
readonly kind: string
|
|
147
|
+
readonly incompleteSearch: boolean
|
|
148
|
+
nextPageToken?: string
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const renderFile = (file: GoogleEmulatorDriveFile, withoutParents = false): Schema.JsonObject => {
|
|
152
|
+
const head: FileHead = {
|
|
153
|
+
kind: 'drive#file',
|
|
154
|
+
id: file.id,
|
|
155
|
+
name: file.name,
|
|
156
|
+
mimeType: file.mimeType,
|
|
157
|
+
starred: file.starred,
|
|
158
|
+
trashed: file.trashed,
|
|
159
|
+
explicitlyTrashed: file.explicitlyTrashed
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Drive answers `trashedTime` only for a trashed item.
|
|
163
|
+
if (file.trashedTime !== undefined) head.trashedTime = file.trashedTime
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
...head,
|
|
167
|
+
parents: withoutParents ? [] : [...file.parents],
|
|
168
|
+
spaces: [...file.spaces],
|
|
169
|
+
version: file.version,
|
|
170
|
+
webViewLink: file.webViewLink,
|
|
171
|
+
iconLink: file.iconLink,
|
|
172
|
+
hasThumbnail: file.hasThumbnail,
|
|
173
|
+
viewedByMe: file.viewedByMe,
|
|
174
|
+
createdTime: file.createdTime,
|
|
175
|
+
modifiedTime: file.modifiedTime,
|
|
176
|
+
ownedByMe: file.ownedByMe,
|
|
177
|
+
shared: file.shared,
|
|
178
|
+
writersCanShare: file.writersCanShare,
|
|
179
|
+
capabilities: file.capabilities
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Every Drive request sends `accept: application/json`, as the fixtures record. */
|
|
184
|
+
const acceptsJson = (request: EmulatedRequest): NotEmulated | undefined =>
|
|
185
|
+
request.header('accept') === 'application/json'
|
|
186
|
+
? undefined
|
|
187
|
+
: notEmulated('Drive requests without accept: application/json are not emulated')
|
|
188
|
+
|
|
189
|
+
/** The `supportsAllDrives=true&fields=<file fields>` query of file reads and writes. */
|
|
190
|
+
const fileQuery = (request: EmulatedRequest): NotEmulated | undefined => {
|
|
191
|
+
const query = exactQuery(request, ['supportsAllDrives', 'fields'])
|
|
192
|
+
|
|
193
|
+
if (isNotEmulated(query)) return query
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
recordedValue(query, 'supportsAllDrives', 'true') ??
|
|
197
|
+
(query['fields'] === fileFields
|
|
198
|
+
? undefined
|
|
199
|
+
: notEmulated('a fields selection other than the connector one is not emulated'))
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const findFile = (state: GoogleEmulatorState, id: string) =>
|
|
204
|
+
state.files.find(file => file.id === id)
|
|
205
|
+
|
|
206
|
+
/** A folder items can live in: an implied folder or a stored untrashed folder. */
|
|
207
|
+
const isLiveFolder = (state: GoogleEmulatorState, id: string): boolean => {
|
|
208
|
+
const folder = findFile(state, id)
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
state.impliedFolderIds.includes(id) ||
|
|
212
|
+
(folder !== undefined && folder.mimeType === driveFolderMimeType && !folder.trashed)
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const isCreatedHere = (file: GoogleEmulatorDriveFile): boolean =>
|
|
217
|
+
mintedFolderIdPattern.test(file.id)
|
|
218
|
+
|
|
219
|
+
// Listing.
|
|
220
|
+
|
|
221
|
+
type ListInput = {
|
|
222
|
+
readonly parentId: string
|
|
223
|
+
readonly pageSize: number
|
|
224
|
+
readonly pageToken: string | undefined
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const listFiles: GoogleRoute = statefulRoute(
|
|
228
|
+
drive('GET', '', false, [listCase, fieldsCase, folderCase], false),
|
|
229
|
+
'none',
|
|
230
|
+
(request): ListInput | NotEmulated => {
|
|
231
|
+
const accepts = acceptsJson(request)
|
|
232
|
+
|
|
233
|
+
if (accepts !== undefined) return accepts
|
|
234
|
+
|
|
235
|
+
const query = exactQuery(
|
|
236
|
+
request,
|
|
237
|
+
[
|
|
238
|
+
'pageSize',
|
|
239
|
+
'q',
|
|
240
|
+
'spaces',
|
|
241
|
+
'supportsAllDrives',
|
|
242
|
+
'includeItemsFromAllDrives',
|
|
243
|
+
'corpora',
|
|
244
|
+
'fields'
|
|
245
|
+
],
|
|
246
|
+
['pageToken']
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
if (isNotEmulated(query)) return query
|
|
250
|
+
|
|
251
|
+
const recorded =
|
|
252
|
+
recordedValue(query, 'spaces', 'drive') ??
|
|
253
|
+
recordedValue(query, 'supportsAllDrives', 'true') ??
|
|
254
|
+
recordedValue(query, 'includeItemsFromAllDrives', 'true') ??
|
|
255
|
+
recordedValue(query, 'corpora', 'user') ??
|
|
256
|
+
(query['fields'] === listFields
|
|
257
|
+
? undefined
|
|
258
|
+
: notEmulated('a fields selection other than the connector one is not emulated'))
|
|
259
|
+
|
|
260
|
+
if (recorded !== undefined) return recorded
|
|
261
|
+
|
|
262
|
+
const parentId = parentQueryPattern.exec(query['q'] ?? '')?.[1]
|
|
263
|
+
|
|
264
|
+
if (parentId === undefined) {
|
|
265
|
+
return notEmulated(`q other than '<folder>' in parents and trashed = false is not emulated`)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const size = pageSize(query['pageSize'], 'pageSize', 1, 1000)
|
|
269
|
+
|
|
270
|
+
if (isNotEmulated(size)) return size
|
|
271
|
+
|
|
272
|
+
const pageToken = query['pageToken']
|
|
273
|
+
|
|
274
|
+
return pageToken === ''
|
|
275
|
+
? notEmulated('an empty pageToken is not emulated')
|
|
276
|
+
: { parentId, pageSize: size, pageToken }
|
|
277
|
+
},
|
|
278
|
+
(state, input, { env }) => {
|
|
279
|
+
if (!isLiveFolder(state, input.parentId)) {
|
|
280
|
+
return notEmulated('listing anything but a folder the state holds is not emulated')
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const children = state.files.filter(
|
|
284
|
+
file =>
|
|
285
|
+
file.parents.includes(input.parentId) && (env.drills.listIncludesTrashed || !file.trashed)
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
// Every recorded listing has children; an empty one is no fixture's answer.
|
|
289
|
+
if (children.length === 0) {
|
|
290
|
+
return notEmulated('a folder listing without items is not emulated (no fixture records one)')
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return listPage(
|
|
294
|
+
env,
|
|
295
|
+
'drive',
|
|
296
|
+
`drive\u0000${input.parentId}\u0000${input.pageSize}`,
|
|
297
|
+
children.map(file => renderFile(file)),
|
|
298
|
+
input.pageSize,
|
|
299
|
+
input.pageToken,
|
|
300
|
+
env.drills.drivePageRepeats,
|
|
301
|
+
(page, nextPageToken) => {
|
|
302
|
+
const head: FileListHead = { kind: 'drive#fileList', incompleteSearch: false }
|
|
303
|
+
|
|
304
|
+
// The last page carries no `nextPageToken`.
|
|
305
|
+
if (nextPageToken !== undefined) head.nextPageToken = nextPageToken
|
|
306
|
+
|
|
307
|
+
return googleJson(200, { ...head, files: [...page] })
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
// File reads and the folder lifecycle.
|
|
314
|
+
|
|
315
|
+
const getFile: GoogleRoute = statefulRoute(
|
|
316
|
+
drive('GET', '/{fileId}', false, [fieldsCase, folderCase], true),
|
|
317
|
+
'none',
|
|
318
|
+
request => acceptsJson(request) ?? fileQuery(request) ?? param(request, 'fileId'),
|
|
319
|
+
(state, id, { env }) => {
|
|
320
|
+
const file = findFile(state, id)
|
|
321
|
+
|
|
322
|
+
if (file !== undefined) {
|
|
323
|
+
return answer(() => googleJson(200, renderFile(file, env.drills.getFileWithoutParents)))
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (state.impliedFolderIds.includes(id)) {
|
|
327
|
+
return notEmulated('reading a folder a fixture only names is not emulated')
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// The recorded not-found answer of an item the account does not hold.
|
|
331
|
+
return answer(() =>
|
|
332
|
+
googleJson(404, googleErrorBody(404, `File not found: ${id}.`, 'notFound', 'NOT_FOUND'))
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
type CreateInput = { readonly name: string; readonly parentId: string }
|
|
338
|
+
|
|
339
|
+
const createFolder: GoogleRoute = statefulRoute(
|
|
340
|
+
drive('POST', '', true, [folderCase], false),
|
|
341
|
+
'json',
|
|
342
|
+
(request): CreateInput | NotEmulated => {
|
|
343
|
+
const body =
|
|
344
|
+
acceptsJson(request) ??
|
|
345
|
+
fileQuery(request) ??
|
|
346
|
+
exactBodyKeys(request.json, 'the create body', ['name', 'mimeType', 'parents'])
|
|
347
|
+
|
|
348
|
+
if (isNotEmulated(body)) return body
|
|
349
|
+
|
|
350
|
+
const name = Predicate.isString(body.name) ? body.name : ''
|
|
351
|
+
|
|
352
|
+
if (!isRunText(name, 'folder')) {
|
|
353
|
+
return notEmulated('a folder name other than "yolk-conformance <runId> folder"')
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (body.mimeType !== driveFolderMimeType) {
|
|
357
|
+
return notEmulated('creating anything but a folder is not emulated')
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const parents = body.parents
|
|
361
|
+
|
|
362
|
+
return Array.isArray(parents) && parents.length === 1 && Predicate.isString(parents[0])
|
|
363
|
+
? { name, parentId: parents[0] }
|
|
364
|
+
: notEmulated('a create with other than one parent is not emulated')
|
|
365
|
+
},
|
|
366
|
+
(state, input, { env }) => {
|
|
367
|
+
if (!isLiveFolder(state, input.parentId)) {
|
|
368
|
+
return notEmulated('creating a folder outside a folder the state holds is not emulated')
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return () => {
|
|
372
|
+
// Read the clock before any write: a failing clock writes nothing.
|
|
373
|
+
const at = new Date(env.now()).toISOString()
|
|
374
|
+
const number = state.counters.nextFolderNumber
|
|
375
|
+
|
|
376
|
+
const folder = driveFile({
|
|
377
|
+
id: mintedFolderId(number),
|
|
378
|
+
name: input.name,
|
|
379
|
+
mimeType: driveFolderMimeType,
|
|
380
|
+
parents: [input.parentId],
|
|
381
|
+
createdTime: at
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
state.counters = { ...state.counters, nextFolderNumber: number + 1 }
|
|
385
|
+
state.files = [...state.files, folder]
|
|
386
|
+
|
|
387
|
+
return googleJson(200, renderFile(folder))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
const trashFile: GoogleRoute = statefulRoute(
|
|
393
|
+
drive('PATCH', '/{fileId}', true, [folderCase], true),
|
|
394
|
+
'json',
|
|
395
|
+
(request): string | NotEmulated => {
|
|
396
|
+
const body =
|
|
397
|
+
acceptsJson(request) ??
|
|
398
|
+
fileQuery(request) ??
|
|
399
|
+
exactBodyKeys(request.json, 'the update body', ['trashed'])
|
|
400
|
+
|
|
401
|
+
if (isNotEmulated(body)) return body
|
|
402
|
+
|
|
403
|
+
return body.trashed === true
|
|
404
|
+
? param(request, 'fileId')
|
|
405
|
+
: notEmulated('updates other than { trashed: true } are not emulated')
|
|
406
|
+
},
|
|
407
|
+
(state, id, { env }) => {
|
|
408
|
+
const file = findFile(state, id)
|
|
409
|
+
|
|
410
|
+
if (file === undefined || !isCreatedHere(file) || file.trashed) {
|
|
411
|
+
return notEmulated('trashing anything but a live folder created here is not emulated')
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return () => {
|
|
415
|
+
const at = new Date(env.now()).toISOString()
|
|
416
|
+
|
|
417
|
+
const trashed: GoogleEmulatorDriveFile = {
|
|
418
|
+
...file,
|
|
419
|
+
trashed: true,
|
|
420
|
+
explicitlyTrashed: true,
|
|
421
|
+
trashedTime: at
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
state.files = state.files.map(candidate => (candidate.id === id ? trashed : candidate))
|
|
425
|
+
|
|
426
|
+
return googleJson(200, renderFile(trashed))
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
const deleteFile: GoogleRoute = statefulRoute(
|
|
432
|
+
drive('DELETE', '/{fileId}', true, [folderCase], true),
|
|
433
|
+
'none',
|
|
434
|
+
(request): string | NotEmulated => {
|
|
435
|
+
const accepts = acceptsJson(request)
|
|
436
|
+
|
|
437
|
+
if (accepts !== undefined) return accepts
|
|
438
|
+
|
|
439
|
+
const query = exactQuery(request, ['supportsAllDrives'])
|
|
440
|
+
|
|
441
|
+
if (isNotEmulated(query)) return query
|
|
442
|
+
|
|
443
|
+
return recordedValue(query, 'supportsAllDrives', 'true') ?? param(request, 'fileId')
|
|
444
|
+
},
|
|
445
|
+
(state, id) => {
|
|
446
|
+
const file = findFile(state, id)
|
|
447
|
+
|
|
448
|
+
if (file === undefined || !isCreatedHere(file)) {
|
|
449
|
+
return notEmulated('deleting anything but a folder created here is not emulated')
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (state.files.some(candidate => candidate.parents.includes(id))) {
|
|
453
|
+
return notEmulated('deleting a folder that holds items is not emulated')
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return () => {
|
|
457
|
+
state.files = state.files.filter(candidate => candidate.id !== id)
|
|
458
|
+
|
|
459
|
+
return googleNoContent()
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
/** The Drive routes, in manifest order. */
|
|
465
|
+
export const driveRoutes: ReadonlyArray<GoogleRoute> = [
|
|
466
|
+
listFiles,
|
|
467
|
+
createFolder,
|
|
468
|
+
getFile,
|
|
469
|
+
trashFile,
|
|
470
|
+
deleteFile
|
|
471
|
+
]
|