@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.
Files changed (333) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2135 -0
  3. package/dist/anthropic.d.mts +269 -0
  4. package/dist/anthropic.d.mts.map +1 -0
  5. package/dist/anthropic.mjs +177 -0
  6. package/dist/anthropic.mjs.map +1 -0
  7. package/dist/chat-completions.d.mts +288 -0
  8. package/dist/chat-completions.d.mts.map +1 -0
  9. package/dist/chat-completions.mjs +451 -0
  10. package/dist/chat-completions.mjs.map +1 -0
  11. package/dist/codex.d.mts +265 -0
  12. package/dist/codex.d.mts.map +1 -0
  13. package/dist/codex.mjs +199 -0
  14. package/dist/codex.mjs.map +1 -0
  15. package/dist/dropbox/api.d.mts +63 -0
  16. package/dist/dropbox/api.d.mts.map +1 -0
  17. package/dist/dropbox/api.mjs +565 -0
  18. package/dist/dropbox/api.mjs.map +1 -0
  19. package/dist/dropbox/state.d.mts +136 -0
  20. package/dist/dropbox/state.d.mts.map +1 -0
  21. package/dist/dropbox/state.mjs +209 -0
  22. package/dist/dropbox/state.mjs.map +1 -0
  23. package/dist/dropbox.d.mts +79 -0
  24. package/dist/dropbox.d.mts.map +1 -0
  25. package/dist/dropbox.mjs +124 -0
  26. package/dist/dropbox.mjs.map +1 -0
  27. package/dist/email-fixtures.d.mts +36 -0
  28. package/dist/email-fixtures.d.mts.map +1 -0
  29. package/dist/email-fixtures.mjs +1080 -0
  30. package/dist/email-fixtures.mjs.map +1 -0
  31. package/dist/email.d.mts +160 -0
  32. package/dist/email.d.mts.map +1 -0
  33. package/dist/email.mjs +608 -0
  34. package/dist/email.mjs.map +1 -0
  35. package/dist/emulator-compose.d.mts +40 -0
  36. package/dist/emulator-compose.d.mts.map +1 -0
  37. package/dist/emulator-compose.mjs +91 -0
  38. package/dist/emulator-compose.mjs.map +1 -0
  39. package/dist/emulator-http.d.mts +53 -0
  40. package/dist/emulator-http.d.mts.map +1 -0
  41. package/dist/emulator-http.mjs +117 -0
  42. package/dist/emulator-http.mjs.map +1 -0
  43. package/dist/emulator-kernel.d.mts +176 -0
  44. package/dist/emulator-kernel.d.mts.map +1 -0
  45. package/dist/emulator-kernel.mjs +413 -0
  46. package/dist/emulator-kernel.mjs.map +1 -0
  47. package/dist/fixture-route.d.mts +129 -0
  48. package/dist/fixture-route.d.mts.map +1 -0
  49. package/dist/fixture-route.mjs +257 -0
  50. package/dist/fixture-route.mjs.map +1 -0
  51. package/dist/fortnox/api.d.mts +92 -0
  52. package/dist/fortnox/api.d.mts.map +1 -0
  53. package/dist/fortnox/api.mjs +750 -0
  54. package/dist/fortnox/api.mjs.map +1 -0
  55. package/dist/fortnox/state.d.mts +533 -0
  56. package/dist/fortnox/state.d.mts.map +1 -0
  57. package/dist/fortnox/state.mjs +612 -0
  58. package/dist/fortnox/state.mjs.map +1 -0
  59. package/dist/fortnox.d.mts +128 -0
  60. package/dist/fortnox.d.mts.map +1 -0
  61. package/dist/fortnox.mjs +403 -0
  62. package/dist/fortnox.mjs.map +1 -0
  63. package/dist/gateway-evaluate-recordings.d.mts +12 -0
  64. package/dist/gateway-evaluate-recordings.d.mts.map +1 -0
  65. package/dist/gateway-evaluate-recordings.mjs +128 -0
  66. package/dist/gateway-evaluate-recordings.mjs.map +1 -0
  67. package/dist/gateway.d.mts +272 -0
  68. package/dist/gateway.d.mts.map +1 -0
  69. package/dist/gateway.mjs +400 -0
  70. package/dist/gateway.mjs.map +1 -0
  71. package/dist/github/api.d.mts +63 -0
  72. package/dist/github/api.d.mts.map +1 -0
  73. package/dist/github/api.mjs +527 -0
  74. package/dist/github/api.mjs.map +1 -0
  75. package/dist/github/state.d.mts +272 -0
  76. package/dist/github/state.d.mts.map +1 -0
  77. package/dist/github/state.mjs +303 -0
  78. package/dist/github/state.mjs.map +1 -0
  79. package/dist/github.d.mts +77 -0
  80. package/dist/github.d.mts.map +1 -0
  81. package/dist/github.mjs +180 -0
  82. package/dist/github.mjs.map +1 -0
  83. package/dist/google/calendar.d.mts +10 -0
  84. package/dist/google/calendar.d.mts.map +1 -0
  85. package/dist/google/calendar.mjs +252 -0
  86. package/dist/google/calendar.mjs.map +1 -0
  87. package/dist/google/drive.d.mts +8 -0
  88. package/dist/google/drive.d.mts.map +1 -0
  89. package/dist/google/drive.mjs +252 -0
  90. package/dist/google/drive.mjs.map +1 -0
  91. package/dist/google/gmail.d.mts +10 -0
  92. package/dist/google/gmail.d.mts.map +1 -0
  93. package/dist/google/gmail.mjs +591 -0
  94. package/dist/google/gmail.mjs.map +1 -0
  95. package/dist/google/shared.d.mts +107 -0
  96. package/dist/google/shared.d.mts.map +1 -0
  97. package/dist/google/shared.mjs +146 -0
  98. package/dist/google/shared.mjs.map +1 -0
  99. package/dist/google/state.d.mts +385 -0
  100. package/dist/google/state.d.mts.map +1 -0
  101. package/dist/google/state.mjs +496 -0
  102. package/dist/google/state.mjs.map +1 -0
  103. package/dist/google.d.mts +75 -0
  104. package/dist/google.d.mts.map +1 -0
  105. package/dist/google.mjs +210 -0
  106. package/dist/google.mjs.map +1 -0
  107. package/dist/linkedin-search/api.d.mts +45 -0
  108. package/dist/linkedin-search/api.d.mts.map +1 -0
  109. package/dist/linkedin-search/api.mjs +181 -0
  110. package/dist/linkedin-search/api.mjs.map +1 -0
  111. package/dist/linkedin-search/state.d.mts +123 -0
  112. package/dist/linkedin-search/state.d.mts.map +1 -0
  113. package/dist/linkedin-search/state.mjs +261 -0
  114. package/dist/linkedin-search/state.mjs.map +1 -0
  115. package/dist/linkedin-search.d.mts +69 -0
  116. package/dist/linkedin-search.d.mts.map +1 -0
  117. package/dist/linkedin-search.mjs +158 -0
  118. package/dist/linkedin-search.mjs.map +1 -0
  119. package/dist/mcp/api.d.mts +44 -0
  120. package/dist/mcp/api.d.mts.map +1 -0
  121. package/dist/mcp/api.mjs +557 -0
  122. package/dist/mcp/api.mjs.map +1 -0
  123. package/dist/mcp/recordings.d.mts +40 -0
  124. package/dist/mcp/recordings.d.mts.map +1 -0
  125. package/dist/mcp/recordings.mjs +2390 -0
  126. package/dist/mcp/recordings.mjs.map +1 -0
  127. package/dist/mcp/state.d.mts +71 -0
  128. package/dist/mcp/state.d.mts.map +1 -0
  129. package/dist/mcp/state.mjs +83 -0
  130. package/dist/mcp/state.mjs.map +1 -0
  131. package/dist/mcp.d.mts +84 -0
  132. package/dist/mcp.d.mts.map +1 -0
  133. package/dist/mcp.mjs +241 -0
  134. package/dist/mcp.mjs.map +1 -0
  135. package/dist/messages.d.mts +183 -0
  136. package/dist/messages.d.mts.map +1 -0
  137. package/dist/messages.mjs +532 -0
  138. package/dist/messages.mjs.map +1 -0
  139. package/dist/microsoft/api.d.mts +47 -0
  140. package/dist/microsoft/api.d.mts.map +1 -0
  141. package/dist/microsoft/api.mjs +178 -0
  142. package/dist/microsoft/api.mjs.map +1 -0
  143. package/dist/microsoft/calendar.d.mts +30 -0
  144. package/dist/microsoft/calendar.d.mts.map +1 -0
  145. package/dist/microsoft/calendar.mjs +271 -0
  146. package/dist/microsoft/calendar.mjs.map +1 -0
  147. package/dist/microsoft/drive.d.mts +36 -0
  148. package/dist/microsoft/drive.d.mts.map +1 -0
  149. package/dist/microsoft/drive.mjs +298 -0
  150. package/dist/microsoft/drive.mjs.map +1 -0
  151. package/dist/microsoft/graph.d.mts +198 -0
  152. package/dist/microsoft/graph.d.mts.map +1 -0
  153. package/dist/microsoft/graph.mjs +264 -0
  154. package/dist/microsoft/graph.mjs.map +1 -0
  155. package/dist/microsoft/mail.d.mts +48 -0
  156. package/dist/microsoft/mail.d.mts.map +1 -0
  157. package/dist/microsoft/mail.mjs +488 -0
  158. package/dist/microsoft/mail.mjs.map +1 -0
  159. package/dist/microsoft/state.d.mts +480 -0
  160. package/dist/microsoft/state.d.mts.map +1 -0
  161. package/dist/microsoft/state.mjs +625 -0
  162. package/dist/microsoft/state.mjs.map +1 -0
  163. package/dist/microsoft.d.mts +168 -0
  164. package/dist/microsoft.d.mts.map +1 -0
  165. package/dist/microsoft.mjs +483 -0
  166. package/dist/microsoft.mjs.map +1 -0
  167. package/dist/node.d.mts +47 -0
  168. package/dist/node.d.mts.map +1 -0
  169. package/dist/node.mjs +178 -0
  170. package/dist/node.mjs.map +1 -0
  171. package/dist/notion/api.d.mts +51 -0
  172. package/dist/notion/api.d.mts.map +1 -0
  173. package/dist/notion/api.mjs +507 -0
  174. package/dist/notion/api.mjs.map +1 -0
  175. package/dist/notion/state.d.mts +329 -0
  176. package/dist/notion/state.d.mts.map +1 -0
  177. package/dist/notion/state.mjs +432 -0
  178. package/dist/notion/state.mjs.map +1 -0
  179. package/dist/notion.d.mts +72 -0
  180. package/dist/notion.d.mts.map +1 -0
  181. package/dist/notion.mjs +127 -0
  182. package/dist/notion.mjs.map +1 -0
  183. package/dist/openai.d.mts +165 -0
  184. package/dist/openai.d.mts.map +1 -0
  185. package/dist/openai.mjs +141 -0
  186. package/dist/openai.mjs.map +1 -0
  187. package/dist/opencode-recordings.d.mts +7 -0
  188. package/dist/opencode-recordings.d.mts.map +1 -0
  189. package/dist/opencode-recordings.mjs +222 -0
  190. package/dist/opencode-recordings.mjs.map +1 -0
  191. package/dist/opencode.d.mts +70 -0
  192. package/dist/opencode.d.mts.map +1 -0
  193. package/dist/opencode.mjs +217 -0
  194. package/dist/opencode.mjs.map +1 -0
  195. package/dist/r2-fixtures.d.mts +36 -0
  196. package/dist/r2-fixtures.d.mts.map +1 -0
  197. package/dist/r2-fixtures.mjs +245 -0
  198. package/dist/r2-fixtures.mjs.map +1 -0
  199. package/dist/r2-guard.d.mts +43 -0
  200. package/dist/r2-guard.d.mts.map +1 -0
  201. package/dist/r2-guard.mjs +215 -0
  202. package/dist/r2-guard.mjs.map +1 -0
  203. package/dist/r2.d.mts +166 -0
  204. package/dist/r2.d.mts.map +1 -0
  205. package/dist/r2.mjs +517 -0
  206. package/dist/r2.mjs.map +1 -0
  207. package/dist/responses.d.mts +231 -0
  208. package/dist/responses.d.mts.map +1 -0
  209. package/dist/responses.mjs +556 -0
  210. package/dist/responses.mjs.map +1 -0
  211. package/dist/route-evidence.d.mts +45 -0
  212. package/dist/route-evidence.d.mts.map +1 -0
  213. package/dist/route-evidence.mjs +56 -0
  214. package/dist/route-evidence.mjs.map +1 -0
  215. package/dist/router.d.mts +93 -0
  216. package/dist/router.d.mts.map +1 -0
  217. package/dist/router.mjs +259 -0
  218. package/dist/router.mjs.map +1 -0
  219. package/dist/stateful-core.d.mts +17 -0
  220. package/dist/stateful-core.d.mts.map +1 -0
  221. package/dist/stateful-core.mjs +41 -0
  222. package/dist/stateful-core.mjs.map +1 -0
  223. package/dist/stateful-emulator.d.mts +565 -0
  224. package/dist/stateful-emulator.d.mts.map +1 -0
  225. package/dist/stateful-emulator.mjs +1228 -0
  226. package/dist/stateful-emulator.mjs.map +1 -0
  227. package/dist/stateful-secrets.d.mts +84 -0
  228. package/dist/stateful-secrets.d.mts.map +1 -0
  229. package/dist/stateful-secrets.mjs +216 -0
  230. package/dist/stateful-secrets.mjs.map +1 -0
  231. package/dist/subscription-usage-recordings.d.mts +9 -0
  232. package/dist/subscription-usage-recordings.d.mts.map +1 -0
  233. package/dist/subscription-usage-recordings.mjs +53 -0
  234. package/dist/subscription-usage-recordings.mjs.map +1 -0
  235. package/dist/subscription-usage.d.mts +53 -0
  236. package/dist/subscription-usage.d.mts.map +1 -0
  237. package/dist/subscription-usage.mjs +21 -0
  238. package/dist/subscription-usage.mjs.map +1 -0
  239. package/dist/telegram/api.d.mts +34 -0
  240. package/dist/telegram/api.d.mts.map +1 -0
  241. package/dist/telegram/api.mjs +257 -0
  242. package/dist/telegram/api.mjs.map +1 -0
  243. package/dist/telegram/state.d.mts +128 -0
  244. package/dist/telegram/state.d.mts.map +1 -0
  245. package/dist/telegram/state.mjs +154 -0
  246. package/dist/telegram/state.mjs.map +1 -0
  247. package/dist/telegram.d.mts +62 -0
  248. package/dist/telegram.d.mts.map +1 -0
  249. package/dist/telegram.mjs +127 -0
  250. package/dist/telegram.mjs.map +1 -0
  251. package/dist/todoist/api.d.mts +55 -0
  252. package/dist/todoist/api.d.mts.map +1 -0
  253. package/dist/todoist/api.mjs +415 -0
  254. package/dist/todoist/api.mjs.map +1 -0
  255. package/dist/todoist/state.d.mts +275 -0
  256. package/dist/todoist/state.d.mts.map +1 -0
  257. package/dist/todoist/state.mjs +345 -0
  258. package/dist/todoist/state.mjs.map +1 -0
  259. package/dist/todoist.d.mts +68 -0
  260. package/dist/todoist.d.mts.map +1 -0
  261. package/dist/todoist.mjs +181 -0
  262. package/dist/todoist.mjs.map +1 -0
  263. package/dist/xai.d.mts +267 -0
  264. package/dist/xai.d.mts.map +1 -0
  265. package/dist/xai.mjs +252 -0
  266. package/dist/xai.mjs.map +1 -0
  267. package/package.json +157 -0
  268. package/src/anthropic.ts +289 -0
  269. package/src/chat-completions.ts +924 -0
  270. package/src/codex.ts +296 -0
  271. package/src/dropbox/api.ts +1084 -0
  272. package/src/dropbox/state.ts +364 -0
  273. package/src/dropbox.ts +203 -0
  274. package/src/email-fixtures.ts +1297 -0
  275. package/src/email.ts +1108 -0
  276. package/src/emulator-compose.ts +147 -0
  277. package/src/emulator-http.ts +184 -0
  278. package/src/emulator-kernel.ts +844 -0
  279. package/src/fixture-route.ts +561 -0
  280. package/src/fortnox/api.ts +1352 -0
  281. package/src/fortnox/state.ts +798 -0
  282. package/src/fortnox.ts +801 -0
  283. package/src/gateway-evaluate-recordings.ts +153 -0
  284. package/src/gateway.ts +530 -0
  285. package/src/github/api.ts +986 -0
  286. package/src/github/state.ts +439 -0
  287. package/src/github.ts +271 -0
  288. package/src/google/calendar.ts +486 -0
  289. package/src/google/drive.ts +471 -0
  290. package/src/google/gmail.ts +1011 -0
  291. package/src/google/shared.ts +321 -0
  292. package/src/google/state.ts +686 -0
  293. package/src/google.ts +298 -0
  294. package/src/linkedin-search/api.ts +363 -0
  295. package/src/linkedin-search/state.ts +373 -0
  296. package/src/linkedin-search.ts +241 -0
  297. package/src/mcp/api.ts +995 -0
  298. package/src/mcp/recordings.ts +2665 -0
  299. package/src/mcp/state.ts +125 -0
  300. package/src/mcp.ts +319 -0
  301. package/src/messages.ts +844 -0
  302. package/src/microsoft/api.ts +426 -0
  303. package/src/microsoft/calendar.ts +491 -0
  304. package/src/microsoft/drive.ts +541 -0
  305. package/src/microsoft/graph.ts +550 -0
  306. package/src/microsoft/mail.ts +833 -0
  307. package/src/microsoft/state.ts +822 -0
  308. package/src/microsoft.ts +982 -0
  309. package/src/node.ts +293 -0
  310. package/src/notion/api.ts +976 -0
  311. package/src/notion/state.ts +512 -0
  312. package/src/notion.ts +210 -0
  313. package/src/openai.ts +198 -0
  314. package/src/opencode-recordings.ts +257 -0
  315. package/src/opencode.ts +271 -0
  316. package/src/r2-fixtures.ts +295 -0
  317. package/src/r2-guard.ts +323 -0
  318. package/src/r2.ts +890 -0
  319. package/src/responses.ts +901 -0
  320. package/src/route-evidence.ts +90 -0
  321. package/src/router.ts +490 -0
  322. package/src/stateful-core.ts +70 -0
  323. package/src/stateful-emulator.ts +2571 -0
  324. package/src/stateful-secrets.ts +299 -0
  325. package/src/subscription-usage-recordings.ts +77 -0
  326. package/src/subscription-usage.ts +68 -0
  327. package/src/telegram/api.ts +437 -0
  328. package/src/telegram/state.ts +229 -0
  329. package/src/telegram.ts +227 -0
  330. package/src/todoist/api.ts +736 -0
  331. package/src/todoist/state.ts +456 -0
  332. package/src/todoist.ts +316 -0
  333. 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
+ ]