digital-tools 2.1.3 → 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 (294) hide show
  1. package/CHANGELOG.md +9 -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 +21 -11
  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 +31 -14
  132. package/src/client.ts +136 -0
  133. package/src/define.ts +30 -24
  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 -4
  199. package/LICENSE +0 -21
  200. package/dist/providers/voice/vapi.d.ts +0 -27
  201. package/dist/providers/voice/vapi.d.ts.map +0 -1
  202. package/dist/providers/voice/vapi.js +0 -440
  203. package/dist/providers/voice/vapi.js.map +0 -1
  204. package/src/define.js +0 -259
  205. package/src/entities/advertising.js +0 -999
  206. package/src/entities/ai.js +0 -756
  207. package/src/entities/analytics.js +0 -1588
  208. package/src/entities/automation.js +0 -601
  209. package/src/entities/communication.js +0 -1150
  210. package/src/entities/crm.js +0 -1386
  211. package/src/entities/design.js +0 -546
  212. package/src/entities/development.js +0 -2212
  213. package/src/entities/document.js +0 -874
  214. package/src/entities/ecommerce.js +0 -1429
  215. package/src/entities/experiment.js +0 -1039
  216. package/src/entities/finance.js +0 -3478
  217. package/src/entities/forms.js +0 -1892
  218. package/src/entities/hr.js +0 -661
  219. package/src/entities/identity.js +0 -997
  220. package/src/entities/index.js +0 -282
  221. package/src/entities/infrastructure.js +0 -1153
  222. package/src/entities/knowledge.js +0 -1438
  223. package/src/entities/marketing.js +0 -1610
  224. package/src/entities/media.js +0 -1634
  225. package/src/entities/notification.js +0 -1199
  226. package/src/entities/presentation.js +0 -1274
  227. package/src/entities/productivity.js +0 -1317
  228. package/src/entities/project-management.js +0 -1136
  229. package/src/entities/recruiting.js +0 -736
  230. package/src/entities/shipping.js +0 -509
  231. package/src/entities/signature.js +0 -1102
  232. package/src/entities/site.js +0 -222
  233. package/src/entities/spreadsheet.js +0 -1341
  234. package/src/entities/storage.js +0 -1198
  235. package/src/entities/support.js +0 -1166
  236. package/src/entities/video-conferencing.js +0 -1750
  237. package/src/entities/video.js +0 -950
  238. package/src/entities.js +0 -1663
  239. package/src/index.js +0 -74
  240. package/src/providers/analytics/index.js +0 -17
  241. package/src/providers/analytics/mixpanel.js +0 -255
  242. package/src/providers/calendar/cal-com.js +0 -303
  243. package/src/providers/calendar/google-calendar.js +0 -335
  244. package/src/providers/calendar/index.js +0 -20
  245. package/src/providers/crm/hubspot.js +0 -566
  246. package/src/providers/crm/index.js +0 -17
  247. package/src/providers/development/github.js +0 -472
  248. package/src/providers/development/index.js +0 -17
  249. package/src/providers/ecommerce/index.js +0 -17
  250. package/src/providers/ecommerce/shopify.js +0 -378
  251. package/src/providers/email/index.js +0 -20
  252. package/src/providers/email/resend.js +0 -258
  253. package/src/providers/email/sendgrid.js +0 -161
  254. package/src/providers/finance/index.js +0 -17
  255. package/src/providers/finance/stripe.js +0 -549
  256. package/src/providers/forms/index.js +0 -17
  257. package/src/providers/forms/typeform.js +0 -500
  258. package/src/providers/index.js +0 -123
  259. package/src/providers/knowledge/index.js +0 -17
  260. package/src/providers/knowledge/notion.js +0 -389
  261. package/src/providers/marketing/index.js +0 -17
  262. package/src/providers/marketing/mailchimp.js +0 -443
  263. package/src/providers/media/cloudinary.js +0 -318
  264. package/src/providers/media/index.js +0 -17
  265. package/src/providers/messaging/index.js +0 -20
  266. package/src/providers/messaging/slack.js +0 -393
  267. package/src/providers/messaging/twilio-sms.js +0 -249
  268. package/src/providers/project-management/index.js +0 -17
  269. package/src/providers/project-management/linear.js +0 -575
  270. package/src/providers/registry.js +0 -86
  271. package/src/providers/spreadsheet/google-sheets.js +0 -375
  272. package/src/providers/spreadsheet/index.js +0 -20
  273. package/src/providers/spreadsheet/xlsx.js +0 -423
  274. package/src/providers/storage/index.js +0 -24
  275. package/src/providers/storage/s3.js +0 -419
  276. package/src/providers/support/index.js +0 -17
  277. package/src/providers/support/zendesk.js +0 -373
  278. package/src/providers/tasks/index.js +0 -17
  279. package/src/providers/tasks/todoist.js +0 -286
  280. package/src/providers/types.js +0 -9
  281. package/src/providers/video-conferencing/google-meet.js +0 -286
  282. package/src/providers/video-conferencing/index.js +0 -31
  283. package/src/providers/video-conferencing/jitsi.js +0 -254
  284. package/src/providers/video-conferencing/teams.js +0 -270
  285. package/src/providers/video-conferencing/zoom.js +0 -332
  286. package/src/registry.js +0 -128
  287. package/src/tools/communication.js +0 -184
  288. package/src/tools/data.js +0 -205
  289. package/src/tools/index.js +0 -11
  290. package/src/tools/web.js +0 -137
  291. package/src/types.js +0 -10
  292. package/test/define.test.js +0 -306
  293. package/test/registry.test.js +0 -357
  294. 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
+ })