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.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -0
- package/dist/client.d.ts +109 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +69 -0
- package/dist/client.js.map +1 -0
- package/dist/define.d.ts +2 -2
- package/dist/define.d.ts.map +1 -1
- package/dist/define.js +21 -11
- package/dist/define.js.map +1 -1
- package/dist/function-ref.d.ts +229 -0
- package/dist/function-ref.d.ts.map +1 -0
- package/dist/function-ref.js +28 -0
- package/dist/function-ref.js.map +1 -0
- package/dist/function-sugar.d.ts +57 -0
- package/dist/function-sugar.d.ts.map +1 -0
- package/dist/function-sugar.js +79 -0
- package/dist/function-sugar.js.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/dist/providers/analytics/mixpanel.d.ts.map +1 -1
- package/dist/providers/analytics/mixpanel.js +21 -18
- package/dist/providers/analytics/mixpanel.js.map +1 -1
- package/dist/providers/calendar/cal-com.d.ts.map +1 -1
- package/dist/providers/calendar/cal-com.js +10 -10
- package/dist/providers/calendar/cal-com.js.map +1 -1
- package/dist/providers/calendar/google-calendar.d.ts.map +1 -1
- package/dist/providers/calendar/google-calendar.js +4 -4
- package/dist/providers/calendar/google-calendar.js.map +1 -1
- package/dist/providers/crm/hubspot.d.ts.map +1 -1
- package/dist/providers/crm/hubspot.js +107 -85
- package/dist/providers/crm/hubspot.js.map +1 -1
- package/dist/providers/development/github.d.ts.map +1 -1
- package/dist/providers/development/github.js +40 -43
- package/dist/providers/development/github.js.map +1 -1
- package/dist/providers/ecommerce/shopify.d.ts.map +1 -1
- package/dist/providers/ecommerce/shopify.js +79 -62
- package/dist/providers/ecommerce/shopify.js.map +1 -1
- package/dist/providers/email/resend.d.ts.map +1 -1
- package/dist/providers/email/resend.js +20 -16
- package/dist/providers/email/resend.js.map +1 -1
- package/dist/providers/email/sendgrid.d.ts.map +1 -1
- package/dist/providers/email/sendgrid.js +12 -9
- package/dist/providers/email/sendgrid.js.map +1 -1
- package/dist/providers/finance/stripe.d.ts.map +1 -1
- package/dist/providers/finance/stripe.js +44 -42
- package/dist/providers/finance/stripe.js.map +1 -1
- package/dist/providers/forms/typeform.d.ts.map +1 -1
- package/dist/providers/forms/typeform.js +68 -58
- package/dist/providers/forms/typeform.js.map +1 -1
- package/dist/providers/knowledge/notion.d.ts.map +1 -1
- package/dist/providers/knowledge/notion.js +75 -41
- package/dist/providers/knowledge/notion.js.map +1 -1
- package/dist/providers/marketing/mailchimp.d.ts.map +1 -1
- package/dist/providers/marketing/mailchimp.js +74 -61
- package/dist/providers/marketing/mailchimp.js.map +1 -1
- package/dist/providers/media/cloudinary.d.ts.map +1 -1
- package/dist/providers/media/cloudinary.js +30 -28
- package/dist/providers/media/cloudinary.js.map +1 -1
- package/dist/providers/messaging/slack.d.ts.map +1 -1
- package/dist/providers/messaging/slack.js +75 -58
- package/dist/providers/messaging/slack.js.map +1 -1
- package/dist/providers/messaging/twilio-sms.d.ts.map +1 -1
- package/dist/providers/messaging/twilio-sms.js +33 -15
- package/dist/providers/messaging/twilio-sms.js.map +1 -1
- package/dist/providers/project-management/linear.d.ts.map +1 -1
- package/dist/providers/project-management/linear.js +31 -27
- package/dist/providers/project-management/linear.js.map +1 -1
- package/dist/providers/spreadsheet/google-sheets.d.ts.map +1 -1
- package/dist/providers/spreadsheet/google-sheets.js +21 -18
- package/dist/providers/spreadsheet/google-sheets.js.map +1 -1
- package/dist/providers/spreadsheet/xlsx.d.ts.map +1 -1
- package/dist/providers/spreadsheet/xlsx.js +4 -4
- package/dist/providers/spreadsheet/xlsx.js.map +1 -1
- package/dist/providers/storage/index.js +1 -0
- package/dist/providers/storage/index.js.map +1 -1
- package/dist/providers/storage/s3.d.ts.map +1 -1
- package/dist/providers/storage/s3.js +36 -27
- package/dist/providers/storage/s3.js.map +1 -1
- package/dist/providers/support/zendesk.d.ts.map +1 -1
- package/dist/providers/support/zendesk.js +24 -25
- package/dist/providers/support/zendesk.js.map +1 -1
- package/dist/providers/tasks/todoist.d.ts.map +1 -1
- package/dist/providers/tasks/todoist.js +18 -18
- package/dist/providers/tasks/todoist.js.map +1 -1
- package/dist/providers/video-conferencing/google-meet.d.ts.map +1 -1
- package/dist/providers/video-conferencing/google-meet.js +11 -11
- package/dist/providers/video-conferencing/google-meet.js.map +1 -1
- package/dist/providers/video-conferencing/jitsi.js +14 -14
- package/dist/providers/video-conferencing/jitsi.js.map +1 -1
- package/dist/providers/video-conferencing/teams.d.ts.map +1 -1
- package/dist/providers/video-conferencing/teams.js +9 -7
- package/dist/providers/video-conferencing/teams.js.map +1 -1
- package/dist/providers/video-conferencing/zoom.d.ts.map +1 -1
- package/dist/providers/video-conferencing/zoom.js +26 -24
- package/dist/providers/video-conferencing/zoom.js.map +1 -1
- package/dist/tools/data.d.ts.map +1 -1
- package/dist/tools/data.js +5 -12
- package/dist/tools/data.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +1 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/system.d.ts +289 -0
- package/dist/tools/system.d.ts.map +1 -0
- package/dist/tools/system.js +752 -0
- package/dist/tools/system.js.map +1 -0
- package/dist/tools/web.d.ts.map +1 -1
- package/dist/tools/web.js +22 -10
- package/dist/tools/web.js.map +1 -1
- package/dist/track-record.d.ts +101 -0
- package/dist/track-record.d.ts.map +1 -0
- package/dist/track-record.js +17 -0
- package/dist/track-record.js.map +1 -0
- package/dist/types.d.ts +210 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/verb-registration.d.ts +122 -0
- package/dist/verb-registration.d.ts.map +1 -0
- package/dist/verb-registration.js +176 -0
- package/dist/verb-registration.js.map +1 -0
- package/dist/worker.d.ts +93 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +315 -0
- package/dist/worker.js.map +1 -0
- package/dist/wrap.d.ts +89 -0
- package/dist/wrap.d.ts.map +1 -0
- package/dist/wrap.js +225 -0
- package/dist/wrap.js.map +1 -0
- package/package.json +31 -14
- package/src/client.ts +136 -0
- package/src/define.ts +30 -24
- package/src/function-ref.ts +264 -0
- package/src/function-sugar.ts +134 -0
- package/src/index.ts +132 -10
- package/src/providers/analytics/mixpanel.ts +19 -18
- package/src/providers/calendar/cal-com.ts +29 -18
- package/src/providers/calendar/google-calendar.ts +20 -14
- package/src/providers/crm/hubspot.ts +225 -99
- package/src/providers/development/github.ts +206 -135
- package/src/providers/ecommerce/shopify.ts +250 -89
- package/src/providers/email/resend.ts +101 -28
- package/src/providers/email/sendgrid.ts +12 -9
- package/src/providers/finance/stripe.ts +128 -49
- package/src/providers/forms/typeform.ts +74 -58
- package/src/providers/knowledge/notion.ts +340 -88
- package/src/providers/marketing/mailchimp.ts +86 -70
- package/src/providers/media/cloudinary.ts +99 -41
- package/src/providers/messaging/slack.ts +283 -85
- package/src/providers/messaging/twilio-sms.ts +35 -15
- package/src/providers/project-management/linear.ts +143 -55
- package/src/providers/spreadsheet/google-sheets.ts +222 -56
- package/src/providers/spreadsheet/xlsx.ts +47 -16
- package/src/providers/storage/s3.ts +119 -47
- package/src/providers/support/zendesk.ts +196 -46
- package/src/providers/tasks/todoist.ts +20 -26
- package/src/providers/video-conferencing/google-meet.ts +17 -20
- package/src/providers/video-conferencing/jitsi.ts +14 -14
- package/src/providers/video-conferencing/teams.ts +14 -13
- package/src/providers/video-conferencing/zoom.ts +54 -49
- package/src/tools/data.ts +6 -16
- package/src/tools/index.ts +1 -0
- package/src/tools/system.ts +887 -0
- package/src/tools/web.ts +22 -10
- package/src/track-record.ts +106 -0
- package/src/types.ts +241 -13
- package/src/verb-registration.ts +197 -0
- package/src/worker.ts +370 -0
- package/src/wrap.ts +260 -0
- package/test/client.test.ts +146 -0
- package/test/communication-tools-extended.test.ts +734 -0
- package/test/data-tools-extended.test.ts +743 -0
- package/test/define-extended.test.ts +819 -0
- package/test/define.test.ts +150 -41
- package/test/entities.test.ts +623 -0
- package/test/extended-entities.test.ts +1228 -0
- package/test/provider-implementations.test.ts +725 -0
- package/test/provider-registry-extended.test.ts +583 -0
- package/test/providers/google-sheets.test.ts +851 -0
- package/test/providers/helpers.ts +554 -0
- package/test/providers/hubspot.test.ts +576 -0
- package/test/providers/slack.test.ts +932 -0
- package/test/providers/stripe.test.ts +701 -0
- package/test/providers.test.ts +578 -0
- package/test/system-tools-extended.test.ts +632 -0
- package/test/system.test.ts +673 -0
- package/test/tools.test.ts +15 -11
- package/test/types.test.ts +402 -0
- package/test/verb-registration.test.ts +395 -0
- package/test/web-tools.test.ts +553 -0
- package/test/worker-extended.test.ts +699 -0
- package/test/worker.test.ts +576 -0
- package/test/wrap.test.ts +366 -0
- package/tsconfig.json +3 -13
- package/vitest.config.ts +37 -0
- package/wrangler.jsonc +9 -0
- package/.turbo/turbo-build.log +0 -4
- package/LICENSE +0 -21
- package/dist/providers/voice/vapi.d.ts +0 -27
- package/dist/providers/voice/vapi.d.ts.map +0 -1
- package/dist/providers/voice/vapi.js +0 -440
- package/dist/providers/voice/vapi.js.map +0 -1
- package/src/define.js +0 -259
- package/src/entities/advertising.js +0 -999
- package/src/entities/ai.js +0 -756
- package/src/entities/analytics.js +0 -1588
- package/src/entities/automation.js +0 -601
- package/src/entities/communication.js +0 -1150
- package/src/entities/crm.js +0 -1386
- package/src/entities/design.js +0 -546
- package/src/entities/development.js +0 -2212
- package/src/entities/document.js +0 -874
- package/src/entities/ecommerce.js +0 -1429
- package/src/entities/experiment.js +0 -1039
- package/src/entities/finance.js +0 -3478
- package/src/entities/forms.js +0 -1892
- package/src/entities/hr.js +0 -661
- package/src/entities/identity.js +0 -997
- package/src/entities/index.js +0 -282
- package/src/entities/infrastructure.js +0 -1153
- package/src/entities/knowledge.js +0 -1438
- package/src/entities/marketing.js +0 -1610
- package/src/entities/media.js +0 -1634
- package/src/entities/notification.js +0 -1199
- package/src/entities/presentation.js +0 -1274
- package/src/entities/productivity.js +0 -1317
- package/src/entities/project-management.js +0 -1136
- package/src/entities/recruiting.js +0 -736
- package/src/entities/shipping.js +0 -509
- package/src/entities/signature.js +0 -1102
- package/src/entities/site.js +0 -222
- package/src/entities/spreadsheet.js +0 -1341
- package/src/entities/storage.js +0 -1198
- package/src/entities/support.js +0 -1166
- package/src/entities/video-conferencing.js +0 -1750
- package/src/entities/video.js +0 -950
- package/src/entities.js +0 -1663
- package/src/index.js +0 -74
- package/src/providers/analytics/index.js +0 -17
- package/src/providers/analytics/mixpanel.js +0 -255
- package/src/providers/calendar/cal-com.js +0 -303
- package/src/providers/calendar/google-calendar.js +0 -335
- package/src/providers/calendar/index.js +0 -20
- package/src/providers/crm/hubspot.js +0 -566
- package/src/providers/crm/index.js +0 -17
- package/src/providers/development/github.js +0 -472
- package/src/providers/development/index.js +0 -17
- package/src/providers/ecommerce/index.js +0 -17
- package/src/providers/ecommerce/shopify.js +0 -378
- package/src/providers/email/index.js +0 -20
- package/src/providers/email/resend.js +0 -258
- package/src/providers/email/sendgrid.js +0 -161
- package/src/providers/finance/index.js +0 -17
- package/src/providers/finance/stripe.js +0 -549
- package/src/providers/forms/index.js +0 -17
- package/src/providers/forms/typeform.js +0 -500
- package/src/providers/index.js +0 -123
- package/src/providers/knowledge/index.js +0 -17
- package/src/providers/knowledge/notion.js +0 -389
- package/src/providers/marketing/index.js +0 -17
- package/src/providers/marketing/mailchimp.js +0 -443
- package/src/providers/media/cloudinary.js +0 -318
- package/src/providers/media/index.js +0 -17
- package/src/providers/messaging/index.js +0 -20
- package/src/providers/messaging/slack.js +0 -393
- package/src/providers/messaging/twilio-sms.js +0 -249
- package/src/providers/project-management/index.js +0 -17
- package/src/providers/project-management/linear.js +0 -575
- package/src/providers/registry.js +0 -86
- package/src/providers/spreadsheet/google-sheets.js +0 -375
- package/src/providers/spreadsheet/index.js +0 -20
- package/src/providers/spreadsheet/xlsx.js +0 -423
- package/src/providers/storage/index.js +0 -24
- package/src/providers/storage/s3.js +0 -419
- package/src/providers/support/index.js +0 -17
- package/src/providers/support/zendesk.js +0 -373
- package/src/providers/tasks/index.js +0 -17
- package/src/providers/tasks/todoist.js +0 -286
- package/src/providers/types.js +0 -9
- package/src/providers/video-conferencing/google-meet.js +0 -286
- package/src/providers/video-conferencing/index.js +0 -31
- package/src/providers/video-conferencing/jitsi.js +0 -254
- package/src/providers/video-conferencing/teams.js +0 -270
- package/src/providers/video-conferencing/zoom.js +0 -332
- package/src/registry.js +0 -128
- package/src/tools/communication.js +0 -184
- package/src/tools/data.js +0 -205
- package/src/tools/index.js +0 -11
- package/src/tools/web.js +0 -137
- package/src/types.js +0 -10
- package/test/define.test.js +0 -306
- package/test/registry.test.js +0 -357
- 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
|
+
})
|