digital-tools 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +2 -0
  3. package/dist/client.d.ts +109 -0
  4. package/dist/client.d.ts.map +1 -0
  5. package/dist/client.js +69 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/define.d.ts +2 -2
  8. package/dist/define.d.ts.map +1 -1
  9. package/dist/define.js +22 -20
  10. package/dist/define.js.map +1 -1
  11. package/dist/function-ref.d.ts +229 -0
  12. package/dist/function-ref.d.ts.map +1 -0
  13. package/dist/function-ref.js +28 -0
  14. package/dist/function-ref.js.map +1 -0
  15. package/dist/function-sugar.d.ts +57 -0
  16. package/dist/function-sugar.d.ts.map +1 -0
  17. package/dist/function-sugar.js +79 -0
  18. package/dist/function-sugar.js.map +1 -0
  19. package/dist/index.d.ts +10 -3
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +24 -4
  22. package/dist/index.js.map +1 -1
  23. package/dist/providers/analytics/mixpanel.d.ts.map +1 -1
  24. package/dist/providers/analytics/mixpanel.js +21 -18
  25. package/dist/providers/analytics/mixpanel.js.map +1 -1
  26. package/dist/providers/calendar/cal-com.d.ts.map +1 -1
  27. package/dist/providers/calendar/cal-com.js +10 -10
  28. package/dist/providers/calendar/cal-com.js.map +1 -1
  29. package/dist/providers/calendar/google-calendar.d.ts.map +1 -1
  30. package/dist/providers/calendar/google-calendar.js +4 -4
  31. package/dist/providers/calendar/google-calendar.js.map +1 -1
  32. package/dist/providers/crm/hubspot.d.ts.map +1 -1
  33. package/dist/providers/crm/hubspot.js +107 -85
  34. package/dist/providers/crm/hubspot.js.map +1 -1
  35. package/dist/providers/development/github.d.ts.map +1 -1
  36. package/dist/providers/development/github.js +40 -43
  37. package/dist/providers/development/github.js.map +1 -1
  38. package/dist/providers/ecommerce/shopify.d.ts.map +1 -1
  39. package/dist/providers/ecommerce/shopify.js +79 -62
  40. package/dist/providers/ecommerce/shopify.js.map +1 -1
  41. package/dist/providers/email/resend.d.ts.map +1 -1
  42. package/dist/providers/email/resend.js +20 -16
  43. package/dist/providers/email/resend.js.map +1 -1
  44. package/dist/providers/email/sendgrid.d.ts.map +1 -1
  45. package/dist/providers/email/sendgrid.js +12 -9
  46. package/dist/providers/email/sendgrid.js.map +1 -1
  47. package/dist/providers/finance/stripe.d.ts.map +1 -1
  48. package/dist/providers/finance/stripe.js +44 -42
  49. package/dist/providers/finance/stripe.js.map +1 -1
  50. package/dist/providers/forms/typeform.d.ts.map +1 -1
  51. package/dist/providers/forms/typeform.js +68 -58
  52. package/dist/providers/forms/typeform.js.map +1 -1
  53. package/dist/providers/knowledge/notion.d.ts.map +1 -1
  54. package/dist/providers/knowledge/notion.js +75 -41
  55. package/dist/providers/knowledge/notion.js.map +1 -1
  56. package/dist/providers/marketing/mailchimp.d.ts.map +1 -1
  57. package/dist/providers/marketing/mailchimp.js +74 -61
  58. package/dist/providers/marketing/mailchimp.js.map +1 -1
  59. package/dist/providers/media/cloudinary.d.ts.map +1 -1
  60. package/dist/providers/media/cloudinary.js +30 -28
  61. package/dist/providers/media/cloudinary.js.map +1 -1
  62. package/dist/providers/messaging/slack.d.ts.map +1 -1
  63. package/dist/providers/messaging/slack.js +75 -58
  64. package/dist/providers/messaging/slack.js.map +1 -1
  65. package/dist/providers/messaging/twilio-sms.d.ts.map +1 -1
  66. package/dist/providers/messaging/twilio-sms.js +33 -15
  67. package/dist/providers/messaging/twilio-sms.js.map +1 -1
  68. package/dist/providers/project-management/linear.d.ts.map +1 -1
  69. package/dist/providers/project-management/linear.js +31 -27
  70. package/dist/providers/project-management/linear.js.map +1 -1
  71. package/dist/providers/spreadsheet/google-sheets.d.ts.map +1 -1
  72. package/dist/providers/spreadsheet/google-sheets.js +21 -18
  73. package/dist/providers/spreadsheet/google-sheets.js.map +1 -1
  74. package/dist/providers/spreadsheet/xlsx.d.ts.map +1 -1
  75. package/dist/providers/spreadsheet/xlsx.js +4 -4
  76. package/dist/providers/spreadsheet/xlsx.js.map +1 -1
  77. package/dist/providers/storage/index.js +1 -0
  78. package/dist/providers/storage/index.js.map +1 -1
  79. package/dist/providers/storage/s3.d.ts.map +1 -1
  80. package/dist/providers/storage/s3.js +36 -27
  81. package/dist/providers/storage/s3.js.map +1 -1
  82. package/dist/providers/support/zendesk.d.ts.map +1 -1
  83. package/dist/providers/support/zendesk.js +24 -25
  84. package/dist/providers/support/zendesk.js.map +1 -1
  85. package/dist/providers/tasks/todoist.d.ts.map +1 -1
  86. package/dist/providers/tasks/todoist.js +18 -18
  87. package/dist/providers/tasks/todoist.js.map +1 -1
  88. package/dist/providers/video-conferencing/google-meet.d.ts.map +1 -1
  89. package/dist/providers/video-conferencing/google-meet.js +11 -11
  90. package/dist/providers/video-conferencing/google-meet.js.map +1 -1
  91. package/dist/providers/video-conferencing/jitsi.js +14 -14
  92. package/dist/providers/video-conferencing/jitsi.js.map +1 -1
  93. package/dist/providers/video-conferencing/teams.d.ts.map +1 -1
  94. package/dist/providers/video-conferencing/teams.js +9 -7
  95. package/dist/providers/video-conferencing/teams.js.map +1 -1
  96. package/dist/providers/video-conferencing/zoom.d.ts.map +1 -1
  97. package/dist/providers/video-conferencing/zoom.js +26 -24
  98. package/dist/providers/video-conferencing/zoom.js.map +1 -1
  99. package/dist/tools/data.d.ts.map +1 -1
  100. package/dist/tools/data.js +5 -12
  101. package/dist/tools/data.js.map +1 -1
  102. package/dist/tools/index.d.ts +1 -0
  103. package/dist/tools/index.d.ts.map +1 -1
  104. package/dist/tools/index.js +1 -0
  105. package/dist/tools/index.js.map +1 -1
  106. package/dist/tools/system.d.ts +289 -0
  107. package/dist/tools/system.d.ts.map +1 -0
  108. package/dist/tools/system.js +752 -0
  109. package/dist/tools/system.js.map +1 -0
  110. package/dist/tools/web.d.ts.map +1 -1
  111. package/dist/tools/web.js +22 -10
  112. package/dist/tools/web.js.map +1 -1
  113. package/dist/track-record.d.ts +101 -0
  114. package/dist/track-record.d.ts.map +1 -0
  115. package/dist/track-record.js +17 -0
  116. package/dist/track-record.js.map +1 -0
  117. package/dist/types.d.ts +210 -9
  118. package/dist/types.d.ts.map +1 -1
  119. package/dist/verb-registration.d.ts +122 -0
  120. package/dist/verb-registration.d.ts.map +1 -0
  121. package/dist/verb-registration.js +176 -0
  122. package/dist/verb-registration.js.map +1 -0
  123. package/dist/worker.d.ts +93 -0
  124. package/dist/worker.d.ts.map +1 -0
  125. package/dist/worker.js +315 -0
  126. package/dist/worker.js.map +1 -0
  127. package/dist/wrap.d.ts +89 -0
  128. package/dist/wrap.d.ts.map +1 -0
  129. package/dist/wrap.js +225 -0
  130. package/dist/wrap.js.map +1 -0
  131. package/package.json +21 -4
  132. package/src/client.ts +136 -0
  133. package/src/define.ts +31 -37
  134. package/src/function-ref.ts +264 -0
  135. package/src/function-sugar.ts +134 -0
  136. package/src/index.ts +132 -10
  137. package/src/providers/analytics/mixpanel.ts +19 -18
  138. package/src/providers/calendar/cal-com.ts +29 -18
  139. package/src/providers/calendar/google-calendar.ts +20 -14
  140. package/src/providers/crm/hubspot.ts +225 -99
  141. package/src/providers/development/github.ts +206 -135
  142. package/src/providers/ecommerce/shopify.ts +250 -89
  143. package/src/providers/email/resend.ts +101 -28
  144. package/src/providers/email/sendgrid.ts +12 -9
  145. package/src/providers/finance/stripe.ts +128 -49
  146. package/src/providers/forms/typeform.ts +74 -58
  147. package/src/providers/knowledge/notion.ts +340 -88
  148. package/src/providers/marketing/mailchimp.ts +86 -70
  149. package/src/providers/media/cloudinary.ts +99 -41
  150. package/src/providers/messaging/slack.ts +283 -85
  151. package/src/providers/messaging/twilio-sms.ts +35 -15
  152. package/src/providers/project-management/linear.ts +143 -55
  153. package/src/providers/spreadsheet/google-sheets.ts +222 -56
  154. package/src/providers/spreadsheet/xlsx.ts +47 -16
  155. package/src/providers/storage/s3.ts +119 -47
  156. package/src/providers/support/zendesk.ts +196 -46
  157. package/src/providers/tasks/todoist.ts +20 -26
  158. package/src/providers/video-conferencing/google-meet.ts +17 -20
  159. package/src/providers/video-conferencing/jitsi.ts +14 -14
  160. package/src/providers/video-conferencing/teams.ts +14 -13
  161. package/src/providers/video-conferencing/zoom.ts +54 -49
  162. package/src/tools/data.ts +6 -16
  163. package/src/tools/index.ts +1 -0
  164. package/src/tools/system.ts +887 -0
  165. package/src/tools/web.ts +22 -10
  166. package/src/track-record.ts +106 -0
  167. package/src/types.ts +241 -13
  168. package/src/verb-registration.ts +197 -0
  169. package/src/worker.ts +370 -0
  170. package/src/wrap.ts +260 -0
  171. package/test/client.test.ts +146 -0
  172. package/test/communication-tools-extended.test.ts +734 -0
  173. package/test/data-tools-extended.test.ts +743 -0
  174. package/test/define-extended.test.ts +819 -0
  175. package/test/define.test.ts +150 -41
  176. package/test/entities.test.ts +623 -0
  177. package/test/extended-entities.test.ts +1228 -0
  178. package/test/provider-implementations.test.ts +725 -0
  179. package/test/provider-registry-extended.test.ts +583 -0
  180. package/test/providers/google-sheets.test.ts +851 -0
  181. package/test/providers/helpers.ts +554 -0
  182. package/test/providers/hubspot.test.ts +576 -0
  183. package/test/providers/slack.test.ts +932 -0
  184. package/test/providers/stripe.test.ts +701 -0
  185. package/test/providers.test.ts +578 -0
  186. package/test/system-tools-extended.test.ts +632 -0
  187. package/test/system.test.ts +673 -0
  188. package/test/tools.test.ts +15 -11
  189. package/test/types.test.ts +402 -0
  190. package/test/verb-registration.test.ts +395 -0
  191. package/test/web-tools.test.ts +553 -0
  192. package/test/worker-extended.test.ts +699 -0
  193. package/test/worker.test.ts +576 -0
  194. package/test/wrap.test.ts +366 -0
  195. package/tsconfig.json +3 -13
  196. package/vitest.config.ts +37 -0
  197. package/wrangler.jsonc +9 -0
  198. package/.turbo/turbo-build.log +0 -5
  199. package/dist/providers/voice/vapi.d.ts +0 -27
  200. package/dist/providers/voice/vapi.d.ts.map +0 -1
  201. package/dist/providers/voice/vapi.js +0 -440
  202. package/dist/providers/voice/vapi.js.map +0 -1
  203. package/src/define.js +0 -267
  204. package/src/entities/advertising.js +0 -999
  205. package/src/entities/ai.js +0 -756
  206. package/src/entities/analytics.js +0 -1588
  207. package/src/entities/automation.js +0 -601
  208. package/src/entities/communication.js +0 -1150
  209. package/src/entities/crm.js +0 -1386
  210. package/src/entities/design.js +0 -546
  211. package/src/entities/development.js +0 -2212
  212. package/src/entities/document.js +0 -874
  213. package/src/entities/ecommerce.js +0 -1429
  214. package/src/entities/experiment.js +0 -1039
  215. package/src/entities/finance.js +0 -3478
  216. package/src/entities/forms.js +0 -1892
  217. package/src/entities/hr.js +0 -661
  218. package/src/entities/identity.js +0 -997
  219. package/src/entities/index.js +0 -282
  220. package/src/entities/infrastructure.js +0 -1153
  221. package/src/entities/knowledge.js +0 -1438
  222. package/src/entities/marketing.js +0 -1610
  223. package/src/entities/media.js +0 -1634
  224. package/src/entities/notification.js +0 -1199
  225. package/src/entities/presentation.js +0 -1274
  226. package/src/entities/productivity.js +0 -1317
  227. package/src/entities/project-management.js +0 -1136
  228. package/src/entities/recruiting.js +0 -736
  229. package/src/entities/shipping.js +0 -509
  230. package/src/entities/signature.js +0 -1102
  231. package/src/entities/site.js +0 -222
  232. package/src/entities/spreadsheet.js +0 -1341
  233. package/src/entities/storage.js +0 -1198
  234. package/src/entities/support.js +0 -1166
  235. package/src/entities/video-conferencing.js +0 -1750
  236. package/src/entities/video.js +0 -950
  237. package/src/entities.js +0 -1663
  238. package/src/index.js +0 -74
  239. package/src/providers/analytics/index.js +0 -17
  240. package/src/providers/analytics/mixpanel.js +0 -255
  241. package/src/providers/calendar/cal-com.js +0 -303
  242. package/src/providers/calendar/google-calendar.js +0 -335
  243. package/src/providers/calendar/index.js +0 -20
  244. package/src/providers/crm/hubspot.js +0 -566
  245. package/src/providers/crm/index.js +0 -17
  246. package/src/providers/development/github.js +0 -472
  247. package/src/providers/development/index.js +0 -17
  248. package/src/providers/ecommerce/index.js +0 -17
  249. package/src/providers/ecommerce/shopify.js +0 -378
  250. package/src/providers/email/index.js +0 -20
  251. package/src/providers/email/resend.js +0 -258
  252. package/src/providers/email/sendgrid.js +0 -161
  253. package/src/providers/finance/index.js +0 -17
  254. package/src/providers/finance/stripe.js +0 -549
  255. package/src/providers/forms/index.js +0 -17
  256. package/src/providers/forms/typeform.js +0 -500
  257. package/src/providers/index.js +0 -123
  258. package/src/providers/knowledge/index.js +0 -17
  259. package/src/providers/knowledge/notion.js +0 -389
  260. package/src/providers/marketing/index.js +0 -17
  261. package/src/providers/marketing/mailchimp.js +0 -443
  262. package/src/providers/media/cloudinary.js +0 -318
  263. package/src/providers/media/index.js +0 -17
  264. package/src/providers/messaging/index.js +0 -20
  265. package/src/providers/messaging/slack.js +0 -393
  266. package/src/providers/messaging/twilio-sms.js +0 -249
  267. package/src/providers/project-management/index.js +0 -17
  268. package/src/providers/project-management/linear.js +0 -575
  269. package/src/providers/registry.js +0 -86
  270. package/src/providers/spreadsheet/google-sheets.js +0 -375
  271. package/src/providers/spreadsheet/index.js +0 -20
  272. package/src/providers/spreadsheet/xlsx.js +0 -423
  273. package/src/providers/storage/index.js +0 -24
  274. package/src/providers/storage/s3.js +0 -419
  275. package/src/providers/support/index.js +0 -17
  276. package/src/providers/support/zendesk.js +0 -373
  277. package/src/providers/tasks/index.js +0 -17
  278. package/src/providers/tasks/todoist.js +0 -286
  279. package/src/providers/types.js +0 -9
  280. package/src/providers/video-conferencing/google-meet.js +0 -286
  281. package/src/providers/video-conferencing/index.js +0 -31
  282. package/src/providers/video-conferencing/jitsi.js +0 -254
  283. package/src/providers/video-conferencing/teams.js +0 -270
  284. package/src/providers/video-conferencing/zoom.js +0 -332
  285. package/src/registry.js +0 -128
  286. package/src/tools/communication.js +0 -184
  287. package/src/tools/data.js +0 -205
  288. package/src/tools/index.js +0 -11
  289. package/src/tools/web.js +0 -137
  290. package/src/types.js +0 -10
  291. package/test/define.test.js +0 -306
  292. package/test/registry.test.js +0 -357
  293. package/test/tools.test.js +0 -363
@@ -0,0 +1,851 @@
1
+ /**
2
+ * Google Sheets Provider Tests
3
+ *
4
+ * Tests for the Google Sheets spreadsheet provider implementation covering:
5
+ * - Provider initialization with access tokens
6
+ * - Spreadsheet creation and management
7
+ * - Sheet operations (add, delete, rename)
8
+ * - Cell range reading and writing
9
+ * - Batch operations
10
+ * - Export functionality
11
+ * - Error handling
12
+ */
13
+
14
+ import { describe, it, expect, beforeEach, afterEach, vi, type MockInstance } from 'vitest'
15
+ import {
16
+ createGoogleSheetsProvider,
17
+ googleSheetsInfo,
18
+ } from '../../src/providers/spreadsheet/google-sheets.js'
19
+ import type { SpreadsheetProvider } from '../../src/providers/types.js'
20
+ import {
21
+ setupMockFetch,
22
+ resetMockFetch,
23
+ mockJsonResponse,
24
+ mockErrorResponse,
25
+ mockNetworkError,
26
+ getLastFetchCall,
27
+ getFetchCall,
28
+ parseFetchJsonBody,
29
+ googleSheetsMocks,
30
+ createTestSpreadsheetData,
31
+ } from './helpers.js'
32
+
33
+ describe('Google Sheets Provider', () => {
34
+ let mockFetch: MockInstance
35
+ let provider: SpreadsheetProvider
36
+
37
+ beforeEach(() => {
38
+ mockFetch = setupMockFetch()
39
+ })
40
+
41
+ afterEach(() => {
42
+ resetMockFetch(mockFetch)
43
+ })
44
+
45
+ // ===========================================================================
46
+ // Provider Initialization Tests
47
+ // ===========================================================================
48
+
49
+ describe('initialization', () => {
50
+ it('should have correct provider info', () => {
51
+ const provider = createGoogleSheetsProvider({})
52
+ expect(provider.info).toBe(googleSheetsInfo)
53
+ expect(provider.info.id).toBe('spreadsheet.google-sheets')
54
+ expect(provider.info.name).toBe('Google Sheets')
55
+ expect(provider.info.category).toBe('spreadsheet')
56
+ })
57
+
58
+ it('should require access token for initialization', async () => {
59
+ provider = createGoogleSheetsProvider({})
60
+ await expect(provider.initialize({})).rejects.toThrow(
61
+ 'Google Sheets access token is required'
62
+ )
63
+ })
64
+
65
+ it('should initialize successfully with valid access token', async () => {
66
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
67
+ await expect(provider.initialize({ accessToken: 'test-token' })).resolves.toBeUndefined()
68
+ })
69
+
70
+ it('should include requiredConfig in provider info', () => {
71
+ provider = createGoogleSheetsProvider({})
72
+ expect(provider.info.requiredConfig).toContain('accessToken')
73
+ })
74
+
75
+ it('should include optionalConfig in provider info', () => {
76
+ provider = createGoogleSheetsProvider({})
77
+ expect(provider.info.optionalConfig).toContain('refreshToken')
78
+ })
79
+ })
80
+
81
+ // ===========================================================================
82
+ // Health Check Tests
83
+ // ===========================================================================
84
+
85
+ describe('healthCheck', () => {
86
+ beforeEach(async () => {
87
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
88
+ await provider.initialize({ accessToken: 'test-token' })
89
+ })
90
+
91
+ it('should return healthy status on successful token check', async () => {
92
+ mockFetch.mockResolvedValueOnce(mockJsonResponse({ access_token: 'valid' }))
93
+
94
+ const health = await provider.healthCheck()
95
+
96
+ expect(health.healthy).toBe(true)
97
+ expect(health.message).toBe('Connected')
98
+ expect(health.latencyMs).toBeGreaterThanOrEqual(0)
99
+ expect(health.checkedAt).toBeInstanceOf(Date)
100
+ })
101
+
102
+ it('should call tokeninfo endpoint for health check', async () => {
103
+ mockFetch.mockResolvedValueOnce(mockJsonResponse({}))
104
+
105
+ await provider.healthCheck()
106
+
107
+ const { url } = getLastFetchCall(mockFetch)
108
+ expect(url).toContain('tokeninfo')
109
+ })
110
+
111
+ it('should return unhealthy status on invalid token', async () => {
112
+ mockFetch.mockResolvedValueOnce(
113
+ mockErrorResponse({ error: { message: 'Invalid token' } }, 401)
114
+ )
115
+
116
+ const health = await provider.healthCheck()
117
+
118
+ expect(health.healthy).toBe(false)
119
+ expect(health.message).toBe('Invalid token')
120
+ })
121
+
122
+ it('should return unhealthy status on network error', async () => {
123
+ mockFetch.mockRejectedValueOnce(mockNetworkError('Connection failed'))
124
+
125
+ const health = await provider.healthCheck()
126
+
127
+ expect(health.healthy).toBe(false)
128
+ expect(health.message).toBe('Connection failed')
129
+ })
130
+ })
131
+
132
+ // ===========================================================================
133
+ // Spreadsheet Creation Tests
134
+ // ===========================================================================
135
+
136
+ describe('create', () => {
137
+ beforeEach(async () => {
138
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
139
+ await provider.initialize({ accessToken: 'test-token' })
140
+ })
141
+
142
+ it('should create spreadsheet with name', async () => {
143
+ mockFetch.mockResolvedValueOnce(
144
+ mockJsonResponse(googleSheetsMocks.spreadsheet('sheet-123', 'My Spreadsheet'))
145
+ )
146
+
147
+ const result = await provider.create('My Spreadsheet')
148
+
149
+ expect(result.id).toBe('sheet-123')
150
+ expect(result.name).toBe('My Spreadsheet')
151
+ })
152
+
153
+ it('should create spreadsheet with custom sheets', async () => {
154
+ mockFetch.mockResolvedValueOnce(
155
+ mockJsonResponse(
156
+ googleSheetsMocks.spreadsheet('sheet-123', 'Test', [
157
+ { id: 0, title: 'Data' },
158
+ { id: 1, title: 'Summary' },
159
+ ])
160
+ )
161
+ )
162
+
163
+ const result = await provider.create('Test', {
164
+ sheets: [{ name: 'Data' }, { name: 'Summary' }],
165
+ })
166
+
167
+ expect(result.sheets).toHaveLength(2)
168
+ expect(result.sheets[0].name).toBe('Data')
169
+ expect(result.sheets[1].name).toBe('Summary')
170
+ })
171
+
172
+ it('should use POST method', async () => {
173
+ mockFetch.mockResolvedValueOnce(
174
+ mockJsonResponse(googleSheetsMocks.spreadsheet('sheet-123', 'Test'))
175
+ )
176
+
177
+ await provider.create('Test')
178
+
179
+ const { options } = getLastFetchCall(mockFetch)
180
+ expect(options?.method).toBe('POST')
181
+ })
182
+
183
+ it('should include locale and timezone when provided', async () => {
184
+ mockFetch.mockResolvedValueOnce(
185
+ mockJsonResponse(googleSheetsMocks.spreadsheet('sheet-123', 'Test'))
186
+ )
187
+
188
+ await provider.create('Test', {
189
+ locale: 'en_US',
190
+ timeZone: 'America/New_York',
191
+ })
192
+
193
+ const body = parseFetchJsonBody(mockFetch) as { properties: Record<string, unknown> }
194
+ expect(body.properties.locale).toBe('en_US')
195
+ expect(body.properties.timeZone).toBe('America/New_York')
196
+ })
197
+
198
+ it('should include Authorization header', async () => {
199
+ mockFetch.mockResolvedValueOnce(
200
+ mockJsonResponse(googleSheetsMocks.spreadsheet('sheet-123', 'Test'))
201
+ )
202
+
203
+ await provider.create('Test')
204
+
205
+ const { options } = getLastFetchCall(mockFetch)
206
+ expect(options?.headers).toHaveProperty('Authorization', 'Bearer test-token')
207
+ })
208
+ })
209
+
210
+ // ===========================================================================
211
+ // Spreadsheet Retrieval Tests
212
+ // ===========================================================================
213
+
214
+ describe('get', () => {
215
+ beforeEach(async () => {
216
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
217
+ await provider.initialize({ accessToken: 'test-token' })
218
+ })
219
+
220
+ it('should retrieve spreadsheet by ID', async () => {
221
+ mockFetch.mockResolvedValueOnce(
222
+ mockJsonResponse(googleSheetsMocks.spreadsheet('sheet-123', 'My Sheet'))
223
+ )
224
+
225
+ const spreadsheet = await provider.get('sheet-123')
226
+
227
+ expect(spreadsheet).not.toBeNull()
228
+ expect(spreadsheet?.id).toBe('sheet-123')
229
+ expect(spreadsheet?.name).toBe('My Sheet')
230
+ })
231
+
232
+ it('should return null for non-existent spreadsheet', async () => {
233
+ mockFetch.mockResolvedValueOnce(mockErrorResponse({ error: { message: 'Not found' } }, 404))
234
+
235
+ const spreadsheet = await provider.get('nonexistent')
236
+
237
+ expect(spreadsheet).toBeNull()
238
+ })
239
+
240
+ it('should include spreadsheet URL', async () => {
241
+ mockFetch.mockResolvedValueOnce(
242
+ mockJsonResponse(googleSheetsMocks.spreadsheet('sheet-123', 'Test'))
243
+ )
244
+
245
+ const spreadsheet = await provider.get('sheet-123')
246
+
247
+ expect(spreadsheet?.url).toContain('sheet-123')
248
+ })
249
+ })
250
+
251
+ // ===========================================================================
252
+ // List Spreadsheets Tests
253
+ // ===========================================================================
254
+
255
+ describe('list', () => {
256
+ beforeEach(async () => {
257
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
258
+ await provider.initialize({ accessToken: 'test-token' })
259
+ })
260
+
261
+ it('should return paginated spreadsheets from Drive', async () => {
262
+ const files = [
263
+ googleSheetsMocks.driveFile('sheet-1', 'Sheet 1'),
264
+ googleSheetsMocks.driveFile('sheet-2', 'Sheet 2'),
265
+ ]
266
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(googleSheetsMocks.driveListResponse(files)))
267
+
268
+ const result = await provider.list!()
269
+
270
+ expect(result.items).toHaveLength(2)
271
+ expect(result.items[0].name).toBe('Sheet 1')
272
+ })
273
+
274
+ it('should apply search query', async () => {
275
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(googleSheetsMocks.driveListResponse([])))
276
+
277
+ await provider.list!({ query: 'budget' })
278
+
279
+ const { url } = getLastFetchCall(mockFetch)
280
+ // URL may use + or %20 for spaces, so check the essential parts
281
+ expect(url).toContain('budget')
282
+ expect(url).toContain('name')
283
+ expect(url).toContain('contains')
284
+ })
285
+
286
+ it('should apply pagination options', async () => {
287
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(googleSheetsMocks.driveListResponse([])))
288
+
289
+ await provider.list!({ limit: 50, cursor: 'pageToken123' })
290
+
291
+ const { url } = getLastFetchCall(mockFetch)
292
+ expect(url).toContain('pageSize=50')
293
+ expect(url).toContain('pageToken=pageToken123')
294
+ })
295
+
296
+ it('should include Drive spreadsheet URL', async () => {
297
+ const files = [googleSheetsMocks.driveFile('sheet-1', 'Sheet 1')]
298
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(googleSheetsMocks.driveListResponse(files)))
299
+
300
+ const result = await provider.list!()
301
+
302
+ expect(result.items[0].url).toContain('docs.google.com/spreadsheets')
303
+ })
304
+ })
305
+
306
+ // ===========================================================================
307
+ // Delete Spreadsheet Tests
308
+ // ===========================================================================
309
+
310
+ describe('delete', () => {
311
+ beforeEach(async () => {
312
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
313
+ await provider.initialize({ accessToken: 'test-token' })
314
+ })
315
+
316
+ it('should delete spreadsheet successfully', async () => {
317
+ mockFetch.mockResolvedValueOnce(mockJsonResponse({}))
318
+
319
+ const result = await provider.delete!('sheet-123')
320
+
321
+ expect(result).toBe(true)
322
+ const { options } = getLastFetchCall(mockFetch)
323
+ expect(options?.method).toBe('DELETE')
324
+ })
325
+
326
+ it('should return false on deletion failure', async () => {
327
+ mockFetch.mockResolvedValueOnce(mockErrorResponse({ error: { message: 'Forbidden' } }, 403))
328
+
329
+ const result = await provider.delete!('sheet-123')
330
+
331
+ expect(result).toBe(false)
332
+ })
333
+ })
334
+
335
+ // ===========================================================================
336
+ // Sheet Operations Tests
337
+ // ===========================================================================
338
+
339
+ describe('getSheet', () => {
340
+ beforeEach(async () => {
341
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
342
+ await provider.initialize({ accessToken: 'test-token' })
343
+ })
344
+
345
+ it('should retrieve sheet by numeric ID', async () => {
346
+ const spreadsheet = {
347
+ ...googleSheetsMocks.spreadsheet('sheet-123', 'Test'),
348
+ sheets: [
349
+ googleSheetsMocks.sheetWithData(0, 'Sheet1', [
350
+ ['A', 'B'],
351
+ [1, 2],
352
+ ]),
353
+ ],
354
+ }
355
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(spreadsheet))
356
+
357
+ const sheet = await provider.getSheet('sheet-123', 0)
358
+
359
+ expect(sheet).not.toBeNull()
360
+ expect(sheet?.id).toBe('0')
361
+ expect(sheet?.name).toBe('Sheet1')
362
+ })
363
+
364
+ it('should retrieve sheet by name', async () => {
365
+ const spreadsheet = {
366
+ ...googleSheetsMocks.spreadsheet('sheet-123', 'Test'),
367
+ sheets: [googleSheetsMocks.sheetWithData(0, 'MySheet', [['Data']])],
368
+ }
369
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(spreadsheet))
370
+
371
+ const sheet = await provider.getSheet('sheet-123', 'MySheet')
372
+
373
+ expect(sheet?.name).toBe('MySheet')
374
+ })
375
+
376
+ it('should return null for non-existent sheet', async () => {
377
+ const spreadsheet = googleSheetsMocks.spreadsheet('sheet-123', 'Test')
378
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(spreadsheet))
379
+
380
+ const sheet = await provider.getSheet('sheet-123', 'NonExistent')
381
+
382
+ expect(sheet).toBeNull()
383
+ })
384
+
385
+ it('should include sheet data when available', async () => {
386
+ const spreadsheet = {
387
+ ...googleSheetsMocks.spreadsheet('sheet-123', 'Test'),
388
+ sheets: [
389
+ googleSheetsMocks.sheetWithData(0, 'Sheet1', [
390
+ ['A', 'B'],
391
+ [1, 2],
392
+ ]),
393
+ ],
394
+ }
395
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(spreadsheet))
396
+
397
+ const sheet = await provider.getSheet('sheet-123', 0)
398
+
399
+ expect(sheet?.data).toBeDefined()
400
+ expect(sheet?.data).toHaveLength(2)
401
+ })
402
+ })
403
+
404
+ describe('addSheet', () => {
405
+ beforeEach(async () => {
406
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
407
+ await provider.initialize({ accessToken: 'test-token' })
408
+ })
409
+
410
+ it('should add new sheet to spreadsheet', async () => {
411
+ mockFetch.mockResolvedValueOnce(
412
+ mockJsonResponse(
413
+ googleSheetsMocks.batchUpdateResponse([
414
+ {
415
+ addSheet: {
416
+ properties: { sheetId: 123, title: 'NewSheet', index: 1 },
417
+ },
418
+ },
419
+ ])
420
+ )
421
+ )
422
+
423
+ const sheet = await provider.addSheet('sheet-123', 'NewSheet')
424
+
425
+ expect(sheet.name).toBe('NewSheet')
426
+ expect(sheet.id).toBe('123')
427
+ })
428
+
429
+ it('should include row and column count options', async () => {
430
+ mockFetch.mockResolvedValueOnce(
431
+ mockJsonResponse(
432
+ googleSheetsMocks.batchUpdateResponse([
433
+ {
434
+ addSheet: {
435
+ properties: {
436
+ sheetId: 123,
437
+ title: 'NewSheet',
438
+ index: 0,
439
+ gridProperties: { rowCount: 500, columnCount: 10 },
440
+ },
441
+ },
442
+ },
443
+ ])
444
+ )
445
+ )
446
+
447
+ await provider.addSheet('sheet-123', 'NewSheet', {
448
+ rowCount: 500,
449
+ columnCount: 10,
450
+ })
451
+
452
+ const body = parseFetchJsonBody(mockFetch) as { requests: unknown[] }
453
+ expect(body.requests).toBeDefined()
454
+ })
455
+
456
+ it('should use batchUpdate endpoint', async () => {
457
+ mockFetch.mockResolvedValueOnce(
458
+ mockJsonResponse(
459
+ googleSheetsMocks.batchUpdateResponse([
460
+ { addSheet: { properties: { sheetId: 1, title: 'Test', index: 0 } } },
461
+ ])
462
+ )
463
+ )
464
+
465
+ await provider.addSheet('sheet-123', 'Test')
466
+
467
+ const { url } = getLastFetchCall(mockFetch)
468
+ expect(url).toContain(':batchUpdate')
469
+ })
470
+ })
471
+
472
+ describe('deleteSheet', () => {
473
+ beforeEach(async () => {
474
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
475
+ await provider.initialize({ accessToken: 'test-token' })
476
+ })
477
+
478
+ it('should delete sheet by numeric ID', async () => {
479
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(googleSheetsMocks.batchUpdateResponse([])))
480
+
481
+ const result = await provider.deleteSheet('sheet-123', 456)
482
+
483
+ expect(result).toBe(true)
484
+ })
485
+
486
+ it('should look up sheet ID when given name', async () => {
487
+ mockFetch
488
+ .mockResolvedValueOnce(
489
+ mockJsonResponse(
490
+ googleSheetsMocks.spreadsheet('sheet-123', 'Test', [{ id: 789, title: 'ToDelete' }])
491
+ )
492
+ )
493
+ .mockResolvedValueOnce(mockJsonResponse(googleSheetsMocks.batchUpdateResponse([])))
494
+
495
+ const result = await provider.deleteSheet('sheet-123', 'ToDelete')
496
+
497
+ expect(result).toBe(true)
498
+ })
499
+
500
+ it('should return false when sheet not found', async () => {
501
+ mockFetch.mockResolvedValueOnce(
502
+ mockJsonResponse(googleSheetsMocks.spreadsheet('sheet-123', 'Test'))
503
+ )
504
+
505
+ const result = await provider.deleteSheet('sheet-123', 'NonExistent')
506
+
507
+ expect(result).toBe(false)
508
+ })
509
+ })
510
+
511
+ describe('renameSheet', () => {
512
+ beforeEach(async () => {
513
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
514
+ await provider.initialize({ accessToken: 'test-token' })
515
+ })
516
+
517
+ it('should rename sheet successfully', async () => {
518
+ mockFetch.mockResolvedValueOnce(mockJsonResponse(googleSheetsMocks.batchUpdateResponse([])))
519
+
520
+ const result = await provider.renameSheet!('sheet-123', 0, 'NewName')
521
+
522
+ expect(result).toBe(true)
523
+ })
524
+ })
525
+
526
+ // ===========================================================================
527
+ // Cell Range Operations Tests
528
+ // ===========================================================================
529
+
530
+ describe('readRange', () => {
531
+ beforeEach(async () => {
532
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
533
+ await provider.initialize({ accessToken: 'test-token' })
534
+ })
535
+
536
+ it('should read cell values from range', async () => {
537
+ mockFetch.mockResolvedValueOnce(
538
+ mockJsonResponse(
539
+ googleSheetsMocks.valueRange('Sheet1!A1:B2', [
540
+ ['Name', 'Value'],
541
+ ['Test', 123],
542
+ ])
543
+ )
544
+ )
545
+
546
+ const data = await provider.readRange('sheet-123', 'Sheet1!A1:B2')
547
+
548
+ expect(data).toHaveLength(2)
549
+ expect(data[0]).toEqual(['Name', 'Value'])
550
+ expect(data[1]).toEqual(['Test', 123])
551
+ })
552
+
553
+ it('should return empty array for empty range', async () => {
554
+ mockFetch.mockResolvedValueOnce(mockJsonResponse({ range: 'Sheet1!A1:B2' }))
555
+
556
+ const data = await provider.readRange('sheet-123', 'Sheet1!A1:B2')
557
+
558
+ expect(data).toEqual([])
559
+ })
560
+
561
+ it('should URL encode range', async () => {
562
+ mockFetch.mockResolvedValueOnce(mockJsonResponse({ values: [] }))
563
+
564
+ await provider.readRange('sheet-123', 'Sheet1!A1:B2')
565
+
566
+ const { url } = getLastFetchCall(mockFetch)
567
+ expect(url).toContain(encodeURIComponent('Sheet1!A1:B2'))
568
+ })
569
+ })
570
+
571
+ describe('writeRange', () => {
572
+ beforeEach(async () => {
573
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
574
+ await provider.initialize({ accessToken: 'test-token' })
575
+ })
576
+
577
+ it('should write values to range', async () => {
578
+ mockFetch.mockResolvedValueOnce(
579
+ mockJsonResponse(googleSheetsMocks.updateResponse('Sheet1!A1:B2', 2, 2, 4))
580
+ )
581
+
582
+ const result = await provider.writeRange('sheet-123', 'Sheet1!A1:B2', [
583
+ ['Name', 'Value'],
584
+ ['Test', 123],
585
+ ])
586
+
587
+ expect(result.updatedRows).toBe(2)
588
+ expect(result.updatedCells).toBe(4)
589
+ })
590
+
591
+ it('should use PUT method', async () => {
592
+ mockFetch.mockResolvedValueOnce(
593
+ mockJsonResponse(googleSheetsMocks.updateResponse('A1:B2', 1, 2, 2))
594
+ )
595
+
596
+ await provider.writeRange('sheet-123', 'A1:B2', [['A', 'B']])
597
+
598
+ const { options } = getLastFetchCall(mockFetch)
599
+ expect(options?.method).toBe('PUT')
600
+ })
601
+
602
+ it('should use USER_ENTERED value input option', async () => {
603
+ mockFetch.mockResolvedValueOnce(
604
+ mockJsonResponse(googleSheetsMocks.updateResponse('A1:B2', 1, 2, 2))
605
+ )
606
+
607
+ await provider.writeRange('sheet-123', 'A1:B2', [['A', 'B']])
608
+
609
+ const { url } = getLastFetchCall(mockFetch)
610
+ expect(url).toContain('valueInputOption=USER_ENTERED')
611
+ })
612
+ })
613
+
614
+ describe('appendRows', () => {
615
+ beforeEach(async () => {
616
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
617
+ await provider.initialize({ accessToken: 'test-token' })
618
+ })
619
+
620
+ it('should append rows to sheet', async () => {
621
+ mockFetch.mockResolvedValueOnce(
622
+ mockJsonResponse({
623
+ spreadsheetId: 'sheet-123',
624
+ updates: { updatedRange: 'Sheet1!A5:B6', updatedRows: 2 },
625
+ })
626
+ )
627
+
628
+ const result = await provider.appendRows('sheet-123', 'Sheet1', [
629
+ ['New', 'Data'],
630
+ ['More', 'Data'],
631
+ ])
632
+
633
+ expect(result.updatedRows).toBe(2)
634
+ })
635
+
636
+ it('should use :append endpoint', async () => {
637
+ mockFetch.mockResolvedValueOnce(
638
+ mockJsonResponse({
639
+ spreadsheetId: 'sheet-123',
640
+ updates: {},
641
+ })
642
+ )
643
+
644
+ await provider.appendRows('sheet-123', 'Sheet1', [['Data']])
645
+
646
+ const { url } = getLastFetchCall(mockFetch)
647
+ expect(url).toContain(':append')
648
+ })
649
+ })
650
+
651
+ describe('clearRange', () => {
652
+ beforeEach(async () => {
653
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
654
+ await provider.initialize({ accessToken: 'test-token' })
655
+ })
656
+
657
+ it('should clear cell range', async () => {
658
+ mockFetch.mockResolvedValueOnce(mockJsonResponse({}))
659
+
660
+ const result = await provider.clearRange('sheet-123', 'Sheet1!A1:B10')
661
+
662
+ expect(result).toBe(true)
663
+ })
664
+
665
+ it('should use :clear endpoint', async () => {
666
+ mockFetch.mockResolvedValueOnce(mockJsonResponse({}))
667
+
668
+ await provider.clearRange('sheet-123', 'A1:B2')
669
+
670
+ const { url } = getLastFetchCall(mockFetch)
671
+ expect(url).toContain(':clear')
672
+ })
673
+ })
674
+
675
+ // ===========================================================================
676
+ // Batch Operations Tests
677
+ // ===========================================================================
678
+
679
+ describe('batchRead', () => {
680
+ beforeEach(async () => {
681
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
682
+ await provider.initialize({ accessToken: 'test-token' })
683
+ })
684
+
685
+ it('should read multiple ranges at once', async () => {
686
+ mockFetch.mockResolvedValueOnce(
687
+ mockJsonResponse({
688
+ valueRanges: [
689
+ { range: 'Sheet1!A1:B2', values: [['A', 'B']] },
690
+ { range: 'Sheet1!C1:D2', values: [['C', 'D']] },
691
+ ],
692
+ })
693
+ )
694
+
695
+ const result = await provider.batchRead!('sheet-123', ['Sheet1!A1:B2', 'Sheet1!C1:D2'])
696
+
697
+ expect(result.size).toBe(2)
698
+ expect(result.get('Sheet1!A1:B2')).toEqual([['A', 'B']])
699
+ })
700
+ })
701
+
702
+ describe('batchWrite', () => {
703
+ beforeEach(async () => {
704
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
705
+ await provider.initialize({ accessToken: 'test-token' })
706
+ })
707
+
708
+ it('should write to multiple ranges at once', async () => {
709
+ mockFetch.mockResolvedValueOnce(
710
+ mockJsonResponse({
711
+ totalUpdatedRows: 4,
712
+ totalUpdatedColumns: 4,
713
+ totalUpdatedCells: 8,
714
+ })
715
+ )
716
+
717
+ const result = await provider.batchWrite!('sheet-123', [
718
+ {
719
+ range: 'Sheet1!A1:B2',
720
+ values: [
721
+ ['A', 'B'],
722
+ [1, 2],
723
+ ],
724
+ },
725
+ {
726
+ range: 'Sheet1!C1:D2',
727
+ values: [
728
+ ['C', 'D'],
729
+ [3, 4],
730
+ ],
731
+ },
732
+ ])
733
+
734
+ expect(result.updatedCells).toBe(8)
735
+ })
736
+ })
737
+
738
+ // ===========================================================================
739
+ // Export Tests
740
+ // ===========================================================================
741
+
742
+ describe('export', () => {
743
+ beforeEach(async () => {
744
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
745
+ await provider.initialize({ accessToken: 'test-token' })
746
+ })
747
+
748
+ it('should export spreadsheet as xlsx', async () => {
749
+ const mockBuffer = new ArrayBuffer(100)
750
+ mockFetch.mockResolvedValueOnce({
751
+ ok: true,
752
+ arrayBuffer: async () => mockBuffer,
753
+ } as Response)
754
+
755
+ const result = await provider.export!('sheet-123', 'xlsx')
756
+
757
+ expect(result).toBeInstanceOf(Buffer)
758
+ const { url } = getLastFetchCall(mockFetch)
759
+ expect(url).toContain('mimeType=')
760
+ })
761
+
762
+ it('should export spreadsheet as csv', async () => {
763
+ const mockBuffer = new ArrayBuffer(50)
764
+ mockFetch.mockResolvedValueOnce({
765
+ ok: true,
766
+ arrayBuffer: async () => mockBuffer,
767
+ } as Response)
768
+
769
+ await provider.export!('sheet-123', 'csv')
770
+
771
+ const { url } = getLastFetchCall(mockFetch)
772
+ expect(url).toContain('text%2Fcsv')
773
+ })
774
+
775
+ it('should export spreadsheet as pdf', async () => {
776
+ const mockBuffer = new ArrayBuffer(200)
777
+ mockFetch.mockResolvedValueOnce({
778
+ ok: true,
779
+ arrayBuffer: async () => mockBuffer,
780
+ } as Response)
781
+
782
+ await provider.export!('sheet-123', 'pdf')
783
+
784
+ const { url } = getLastFetchCall(mockFetch)
785
+ expect(url).toContain('application%2Fpdf')
786
+ })
787
+
788
+ it('should throw on export failure', async () => {
789
+ mockFetch.mockResolvedValueOnce({
790
+ ok: false,
791
+ status: 403,
792
+ } as Response)
793
+
794
+ await expect(provider.export!('sheet-123', 'xlsx')).rejects.toThrow('Export failed')
795
+ })
796
+ })
797
+
798
+ // ===========================================================================
799
+ // Error Handling Tests
800
+ // ===========================================================================
801
+
802
+ describe('error handling', () => {
803
+ beforeEach(async () => {
804
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
805
+ await provider.initialize({ accessToken: 'test-token' })
806
+ })
807
+
808
+ it('should handle permission errors', async () => {
809
+ mockFetch.mockResolvedValueOnce(
810
+ mockErrorResponse(googleSheetsMocks.error('Permission denied', 403), 403)
811
+ )
812
+
813
+ const spreadsheet = await provider.get('sheet-123')
814
+
815
+ expect(spreadsheet).toBeNull()
816
+ })
817
+
818
+ it('should handle rate limit errors', async () => {
819
+ mockFetch.mockResolvedValueOnce(
820
+ mockErrorResponse(googleSheetsMocks.error('Rate Limit Exceeded', 429), 429)
821
+ )
822
+
823
+ await expect(provider.writeRange('sheet-123', 'A1:B2', [['Data']])).rejects.toThrow(
824
+ 'Rate Limit Exceeded'
825
+ )
826
+ })
827
+
828
+ it('should handle invalid range errors', async () => {
829
+ mockFetch.mockResolvedValueOnce(
830
+ mockErrorResponse(googleSheetsMocks.error('Invalid range', 400), 400)
831
+ )
832
+
833
+ await expect(provider.readRange('sheet-123', 'InvalidRange!!!')).rejects.toThrow(
834
+ 'Invalid range'
835
+ )
836
+ })
837
+ })
838
+
839
+ // ===========================================================================
840
+ // Dispose Tests
841
+ // ===========================================================================
842
+
843
+ describe('dispose', () => {
844
+ it('should dispose without error', async () => {
845
+ provider = createGoogleSheetsProvider({ accessToken: 'test-token' })
846
+ await provider.initialize({ accessToken: 'test-token' })
847
+
848
+ await expect(provider.dispose()).resolves.toBeUndefined()
849
+ })
850
+ })
851
+ })