@alumbwe/anvil 1.0.42 → 1.0.50
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/bin/anvil.js +18 -0
- package/package.json +13 -6
- package/src/agents/bundled-agents.generated.ts +1 -1
- package/src/index.tsx +27 -4
- package/src/login/plain-login.ts +60 -0
- package/src/login/supabase-auth.ts +346 -0
- package/src/utils/anvil-client.ts +4 -0
- package/src/utils/auth.ts +16 -4
- package/src/utils/config-dir.ts +1 -1
- package/vendor/@anvil/common/src/constants/deepseek-direct.ts +30 -1
- package/vendor/@anvil/common/src/util/project-ignore.ts +4 -3
- package/vendor/@anvil/sdk/dist/index.cjs +46 -10
- package/vendor/@anvil/sdk/dist/index.mjs +46 -10
- package/src/commands/__tests__/anvil-command-aliases.test.ts +0 -56
- package/src/commands/__tests__/bash-command.test.ts +0 -445
- package/src/commands/__tests__/command-args.test.ts +0 -349
- package/src/commands/__tests__/copy-conversation-clipboard.test.ts +0 -108
- package/src/commands/__tests__/copy-conversation.test.ts +0 -226
- package/src/commands/__tests__/diagnostics-command.test.ts +0 -35
- package/src/commands/__tests__/image.test.ts +0 -99
- package/src/commands/__tests__/init.test.ts +0 -456
- package/src/commands/__tests__/new-command-rotates-chat.test.ts +0 -91
- package/src/commands/__tests__/process-diagnostics.test.ts +0 -54
- package/src/commands/__tests__/prompt-builders.test.ts +0 -28
- package/src/commands/__tests__/router-input.test.ts +0 -298
- package/src/components/__tests__/ad-banner.test.tsx +0 -83
- package/src/components/__tests__/anvil-landing-heading-row.test.tsx +0 -91
- package/src/components/__tests__/anvil-model-selector.test.tsx +0 -519
- package/src/components/__tests__/anvil-offer-invariants.test.ts +0 -54
- package/src/components/__tests__/anvil-takeover-prompt.test.tsx +0 -212
- package/src/components/__tests__/chat-input-bar.test.tsx +0 -130
- package/src/components/__tests__/grid-layout.integration.test.tsx +0 -304
- package/src/components/__tests__/grid-layout.test.tsx +0 -1051
- package/src/components/__tests__/message-block.completion.test.tsx +0 -104
- package/src/components/__tests__/message-block.streaming.test.tsx +0 -78
- package/src/components/__tests__/message-with-agents.test.tsx +0 -579
- package/src/components/__tests__/multiline-input.test.tsx +0 -1132
- package/src/components/__tests__/queue-panel.test.tsx +0 -398
- package/src/components/__tests__/selectable-list.test.ts +0 -232
- package/src/components/__tests__/status-bar.test.tsx +0 -44
- package/src/components/__tests__/status-indicator.test.tsx +0 -204
- package/src/components/__tests__/terminal-thinking-orb.test.tsx +0 -111
- package/src/components/__tests__/user-error-banner.test.tsx +0 -102
- package/src/components/ask-user/__tests__/multiple-choice-form.test.ts +0 -432
- package/src/components/ask-user/__tests__/validation.test.ts +0 -213
- package/src/components/tools/__tests__/apply-patch.test.tsx +0 -84
- package/src/components/tools/__tests__/code-search.test.tsx +0 -45
- package/src/components/tools/__tests__/gravity-index.test.ts +0 -76
- package/src/components/tools/__tests__/render-ui.test.tsx +0 -92
- package/src/components/tools/__tests__/run-terminal-command.test.ts +0 -233
- package/src/contexts/__tests__/chat-runtime-context.test.tsx +0 -149
- package/src/data/slash-commands.test.ts +0 -24
- package/src/hooks/__tests__/holds-live-anvil-slot.test.ts +0 -49
- package/src/hooks/__tests__/session-fetch-signal.test.ts +0 -63
- package/src/hooks/__tests__/use-activity-query.test.ts +0 -1000
- package/src/hooks/__tests__/use-ask-user-bridge.test.ts +0 -176
- package/src/hooks/__tests__/use-auth-query.test.ts +0 -21
- package/src/hooks/__tests__/use-chat-input-remount.test.tsx +0 -66
- package/src/hooks/__tests__/use-clipboard.test.tsx +0 -73
- package/src/hooks/__tests__/use-connection-status.test.ts +0 -112
- package/src/hooks/__tests__/use-directory-browser.test.ts +0 -203
- package/src/hooks/__tests__/use-gravity-ad.test.ts +0 -73
- package/src/hooks/__tests__/use-grid-layout.test.ts +0 -349
- package/src/hooks/__tests__/use-input-history.test.ts +0 -699
- package/src/hooks/__tests__/use-message-queue-editing.test.tsx +0 -209
- package/src/hooks/__tests__/use-message-queue.test.tsx +0 -71
- package/src/hooks/__tests__/use-path-tab-completion.test.ts +0 -315
- package/src/hooks/__tests__/use-queue-controls.test.ts +0 -68
- package/src/hooks/__tests__/use-queue-ui.test.tsx +0 -83
- package/src/hooks/__tests__/use-searchable-list.test.ts +0 -359
- package/src/hooks/__tests__/use-send-message-timer.test.ts +0 -146
- package/src/hooks/__tests__/use-suggestion-engine-mention.test.ts +0 -361
- package/src/hooks/__tests__/use-suggestion-engine.test.ts +0 -485
- package/src/hooks/__tests__/use-terminal-focus.test.ts +0 -66
- package/src/hooks/__tests__/use-terminal-layout.test.ts +0 -675
- package/src/hooks/__tests__/use-timeout.test.ts +0 -330
- package/src/hooks/__tests__/use-usage-query.test.ts +0 -502
- package/src/hooks/__tests__/use-user-details-query.test.ts +0 -195
- package/src/hooks/helpers/__tests__/send-message.test.ts +0 -1918
- package/src/init/__tests__/init-direnv.test.ts +0 -526
- package/src/state/__tests__/chat-store-focus.test.ts +0 -22
- package/src/state/__tests__/feedback-store.test.ts +0 -257
- package/src/utils/__tests__/active-run.test.ts +0 -140
- package/src/utils/__tests__/activity-tracker.test.ts +0 -440
- package/src/utils/__tests__/agent-display.test.ts +0 -139
- package/src/utils/__tests__/analytics-client.test.ts +0 -262
- package/src/utils/__tests__/anonymous-id.test.ts +0 -74
- package/src/utils/__tests__/anvil-api.test.ts +0 -578
- package/src/utils/__tests__/anvil-instance-owner.test.ts +0 -69
- package/src/utils/__tests__/anvil-model-navigation.test.ts +0 -102
- package/src/utils/__tests__/anvil-premium-reset.test.ts +0 -79
- package/src/utils/__tests__/anvil-referral-cache.test.ts +0 -116
- package/src/utils/__tests__/anvil-session-api.test.ts +0 -215
- package/src/utils/__tests__/anvil-session-display.test.ts +0 -23
- package/src/utils/__tests__/anvil-streak-line.test.ts +0 -217
- package/src/utils/__tests__/arrays.test.ts +0 -264
- package/src/utils/__tests__/bash-context-processor.test.ts +0 -38
- package/src/utils/__tests__/block-processor.test.ts +0 -810
- package/src/utils/__tests__/chat-history.test.ts +0 -203
- package/src/utils/__tests__/chat-input-key-intercept.test.ts +0 -40
- package/src/utils/__tests__/chat-meta.test.ts +0 -130
- package/src/utils/__tests__/clipboard-linux.test.ts +0 -359
- package/src/utils/__tests__/clipboard.test.ts +0 -910
- package/src/utils/__tests__/code-search-summary.test.ts +0 -84
- package/src/utils/__tests__/collapse-helpers.test.ts +0 -1109
- package/src/utils/__tests__/error-handling.test.ts +0 -658
- package/src/utils/__tests__/exit-cleanly.test.ts +0 -110
- package/src/utils/__tests__/feedback-helpers.test.ts +0 -444
- package/src/utils/__tests__/feedback-submission.test.ts +0 -26
- package/src/utils/__tests__/fetch-usage.test.ts +0 -355
- package/src/utils/__tests__/fingerprint.test.ts +0 -144
- package/src/utils/__tests__/fixtures/terminal-command-broker-child.ts +0 -7
- package/src/utils/__tests__/fixtures/terminal-command-broker-entry.ts +0 -3
- package/src/utils/__tests__/fixtures/terminal-command-broker-owner.ts +0 -33
- package/src/utils/__tests__/format-elapsed-time.test.ts +0 -103
- package/src/utils/__tests__/format-timeout.test.ts +0 -87
- package/src/utils/__tests__/image-dimensions.test.ts +0 -249
- package/src/utils/__tests__/image-processor.test.ts +0 -209
- package/src/utils/__tests__/implementor-helpers.test.ts +0 -1458
- package/src/utils/__tests__/keyboard-actions.test.ts +0 -660
- package/src/utils/__tests__/layout-helpers.test.ts +0 -36
- package/src/utils/__tests__/lazy-response-ads.test.ts +0 -167
- package/src/utils/__tests__/log-shipper.test.ts +0 -53
- package/src/utils/__tests__/markdown-renderer.test.tsx +0 -421
- package/src/utils/__tests__/message-block-helpers.test.ts +0 -1263
- package/src/utils/__tests__/message-updater.test.ts +0 -645
- package/src/utils/__tests__/osc-timeout-scenarios.test.ts +0 -172
- package/src/utils/__tests__/pending-attachments.test.ts +0 -277
- package/src/utils/__tests__/polling-backoff.test.ts +0 -51
- package/src/utils/__tests__/queue-panel-actions.test.ts +0 -148
- package/src/utils/__tests__/run-state-storage.test.ts +0 -919
- package/src/utils/__tests__/safe-json.test.ts +0 -77
- package/src/utils/__tests__/sdk-event-handlers.test.ts +0 -685
- package/src/utils/__tests__/send-message-helpers.test.ts +0 -1898
- package/src/utils/__tests__/send-message-timer.test.ts +0 -64
- package/src/utils/__tests__/settings.test.ts +0 -99
- package/src/utils/__tests__/strings.test.ts +0 -192
- package/src/utils/__tests__/terminal-color-detection.test.ts +0 -425
- package/src/utils/__tests__/terminal-command-broker.test.ts +0 -528
- package/src/utils/__tests__/terminal-enter-detection.test.ts +0 -77
- package/src/utils/__tests__/terminal-io.test.ts +0 -137
- package/src/utils/__tests__/terminal-protocol-controller.test.ts +0 -119
- package/src/utils/__tests__/text-layout.test.ts +0 -82
- package/src/utils/__tests__/theme-platform-detection.test.ts +0 -125
- package/src/utils/__tests__/think-tag-parser.test.ts +0 -159
- package/src/utils/__tests__/trace-writer.test.ts +0 -223
- package/src/utils/__tests__/trim-chat-logs.test.ts +0 -78
- package/src/utils/__tests__/usage-banner-state.test.ts +0 -298
- package/src/utils/__tests__/validation-error-formatting.test.ts +0 -126
- package/src/utils/__tests__/windows-terminal-health.test.ts +0 -101
- package/src/utils/__tests__/write-file-atomic.test.ts +0 -127
- package/vendor/@anvil/common/src/__tests__/agent-validation.test.ts +0 -846
- package/vendor/@anvil/common/src/__tests__/anvil-model-availability.test.ts +0 -101
- package/vendor/@anvil/common/src/__tests__/anvil-models.test.ts +0 -1408
- package/vendor/@anvil/common/src/__tests__/anvil-public-data-use-copy.test.ts +0 -258
- package/vendor/@anvil/common/src/__tests__/anvil-referral-tiers.test.ts +0 -48
- package/vendor/@anvil/common/src/__tests__/anvil-spend-ceilings.test.ts +0 -282
- package/vendor/@anvil/common/src/__tests__/anvil-trust.test.ts +0 -526
- package/vendor/@anvil/common/src/__tests__/deepseek-direct.test.ts +0 -68
- package/vendor/@anvil/common/src/__tests__/disposable-email.test.ts +0 -132
- package/vendor/@anvil/common/src/__tests__/dynamic-agent-template-schema.test.ts +0 -422
- package/vendor/@anvil/common/src/__tests__/env-ci.test.ts +0 -167
- package/vendor/@anvil/common/src/__tests__/env-process.test.ts +0 -145
- package/vendor/@anvil/common/src/__tests__/foreign-client-shipped-agents.test.ts +0 -234
- package/vendor/@anvil/common/src/__tests__/foreign-client-signals.test.ts +0 -379
- package/vendor/@anvil/common/src/__tests__/free-agents.test.ts +0 -807
- package/vendor/@anvil/common/src/__tests__/gravity-capi.test.ts +0 -154
- package/vendor/@anvil/common/src/__tests__/handlesteps-parsing.test.ts +0 -246
- package/vendor/@anvil/common/src/__tests__/kimi-k3-god-only.test.ts +0 -77
- package/vendor/@anvil/common/src/__tests__/model-config.test.ts +0 -89
- package/vendor/@anvil/common/src/__tests__/project-file-tree.test.ts +0 -298
- package/vendor/@anvil/common/src/__tests__/provisioned-model-tiers.test.ts +0 -116
- package/vendor/@anvil/common/src/__tests__/reasoning-effort.test.ts +0 -167
- package/vendor/@anvil/common/src/__tests__/reddit-capi.test.ts +0 -132
- package/vendor/@anvil/common/src/__tests__/response-ad-positions.test.ts +0 -72
- package/vendor/@anvil/common/src/__tests__/user-state.test.ts +0 -30
- package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding-gate.test.ts +0 -63
- package/vendor/@anvil/common/src/constants/__tests__/anvil-onboarding.test.ts +0 -246
- package/vendor/@anvil/common/src/constants/__tests__/cf-worker-signals.test.ts +0 -106
- package/vendor/@anvil/common/src/testing/TESTING_PATTERNS.md +0 -351
- package/vendor/@anvil/common/src/testing/anvil-offer-invariants.ts +0 -169
- package/vendor/@anvil/common/src/testing/errors.ts +0 -33
- package/vendor/@anvil/common/src/testing/fixtures/agent-runtime.ts +0 -334
- package/vendor/@anvil/common/src/testing/impl/agent-runtime.ts +0 -6
- package/vendor/@anvil/common/src/testing/index.ts +0 -84
- package/vendor/@anvil/common/src/testing/mock-modules.ts +0 -53
- package/vendor/@anvil/common/src/testing/mock-types.ts +0 -123
- package/vendor/@anvil/common/src/testing/mocks/analytics.ts +0 -261
- package/vendor/@anvil/common/src/testing/mocks/child-process.ts +0 -93
- package/vendor/@anvil/common/src/testing/mocks/crypto.ts +0 -218
- package/vendor/@anvil/common/src/testing/mocks/database.ts +0 -337
- package/vendor/@anvil/common/src/testing/mocks/fetch.ts +0 -219
- package/vendor/@anvil/common/src/testing/mocks/filesystem.ts +0 -166
- package/vendor/@anvil/common/src/testing/mocks/index.ts +0 -101
- package/vendor/@anvil/common/src/testing/mocks/logger.ts +0 -135
- package/vendor/@anvil/common/src/testing/mocks/stream.ts +0 -313
- package/vendor/@anvil/common/src/testing/mocks/timers.ts +0 -132
- package/vendor/@anvil/common/src/testing/mocks/tree-sitter.ts +0 -127
- package/vendor/@anvil/common/src/testing/setup.ts +0 -282
- package/vendor/@anvil/common/src/tools/__tests__/compile-tool-definitions.test.ts +0 -34
- package/vendor/@anvil/common/src/tools/params/__tests__/coerce-to-array.test.ts +0 -213
- package/vendor/@anvil/common/src/tools/params/__tests__/read-docs.test.ts +0 -47
- package/vendor/@anvil/common/src/tools/params/__tests__/render-ui.test.ts +0 -65
- package/vendor/@anvil/common/src/tools/params/tool/__tests__/run-terminal-command-timeout.test.ts +0 -49
- package/vendor/@anvil/common/src/types/__tests__/dynamic-agent-template.test.ts +0 -20
- package/vendor/@anvil/common/src/util/__tests__/ad-user-agent.test.ts +0 -26
- package/vendor/@anvil/common/src/util/__tests__/analytics-dispatcher.test.ts +0 -122
- package/vendor/@anvil/common/src/util/__tests__/analytics-log.test.ts +0 -102
- package/vendor/@anvil/common/src/util/__tests__/analytics-sampling.test.ts +0 -143
- package/vendor/@anvil/common/src/util/__tests__/anvil-streak.test.ts +0 -202
- package/vendor/@anvil/common/src/util/__tests__/anvil-usage-summary.test.ts +0 -193
- package/vendor/@anvil/common/src/util/__tests__/axiom-only-log.test.ts +0 -140
- package/vendor/@anvil/common/src/util/__tests__/client-user-agent.test.ts +0 -148
- package/vendor/@anvil/common/src/util/__tests__/engagement-tracker.test.ts +0 -115
- package/vendor/@anvil/common/src/util/__tests__/env-file-path.test.ts +0 -57
- package/vendor/@anvil/common/src/util/__tests__/error-abort.test.ts +0 -774
- package/vendor/@anvil/common/src/util/__tests__/error-api-details.test.ts +0 -206
- package/vendor/@anvil/common/src/util/__tests__/file-read-limits.test.ts +0 -173
- package/vendor/@anvil/common/src/util/__tests__/format-code-search.test.ts +0 -60
- package/vendor/@anvil/common/src/util/__tests__/log-mirror.test.ts +0 -31
- package/vendor/@anvil/common/src/util/__tests__/messages.test.ts +0 -1252
- package/vendor/@anvil/common/src/util/__tests__/partial-json-delta.test.ts +0 -505
- package/vendor/@anvil/common/src/util/__tests__/path.test.ts +0 -22
- package/vendor/@anvil/common/src/util/__tests__/project-ignore.test.ts +0 -90
- package/vendor/@anvil/common/src/util/__tests__/promise.test.ts +0 -325
- package/vendor/@anvil/common/src/util/__tests__/rate-limit.test.ts +0 -28
- package/vendor/@anvil/common/src/util/__tests__/reddit-anvil-retention.test.ts +0 -130
- package/vendor/@anvil/common/src/util/__tests__/saxy.test.ts +0 -1008
- package/vendor/@anvil/common/src/util/__tests__/split-data.test.ts +0 -289
- package/vendor/@anvil/common/src/util/__tests__/string.test.ts +0 -239
- package/vendor/@anvil/common/src/util/__tests__/thread-title.test.ts +0 -70
- package/vendor/@anvil/common/src/util/__tests__/tool-result-media-order.test.ts +0 -108
- package/vendor/@anvil/common/src/util/__tests__/ttft-histogram.test.ts +0 -180
- package/vendor/@anvil/common/src/util/__tests__/with-timeout.test.ts +0 -24
- package/vendor/@anvil/common/src/util/__tests__/zoned-time.test.ts +0 -88
- package/vendor/@anvil/common/src/util/skills.test.ts +0 -44
- package/vendor/@anvil/sdk/dist/index.cjs.map +0 -440
- package/vendor/@anvil/sdk/dist/index.mjs.map +0 -440
|
@@ -1,502 +0,0 @@
|
|
|
1
|
-
import { createMockLogger } from '@anvil/common/testing/mocks/logger'
|
|
2
|
-
import { describe, test, expect, beforeEach, afterEach, mock } from 'bun:test'
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
resetActivityQueryCache,
|
|
6
|
-
getActivityQueryData,
|
|
7
|
-
setActivityQueryData,
|
|
8
|
-
invalidateActivityQuery,
|
|
9
|
-
removeActivityQuery,
|
|
10
|
-
} from '../use-activity-query'
|
|
11
|
-
import {
|
|
12
|
-
fetchUsageData,
|
|
13
|
-
usageQueryKeys,
|
|
14
|
-
} from '../use-usage-query'
|
|
15
|
-
|
|
16
|
-
import type { ClientEnv } from '@anvil/common/types/contracts/env'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
beforeEach(() => {
|
|
20
|
-
resetActivityQueryCache()
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
describe('fetchUsageData', () => {
|
|
24
|
-
const originalFetch = globalThis.fetch
|
|
25
|
-
const originalEnv = process.env.NEXT_PUBLIC_ANVIL_APP_URL
|
|
26
|
-
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
process.env.NEXT_PUBLIC_ANVIL_APP_URL = 'https://test.codebuff.local'
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
globalThis.fetch = originalFetch
|
|
33
|
-
process.env.NEXT_PUBLIC_ANVIL_APP_URL = originalEnv
|
|
34
|
-
mock.restore()
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('should fetch usage data successfully', async () => {
|
|
38
|
-
const mockResponse = {
|
|
39
|
-
type: 'usage-response' as const,
|
|
40
|
-
usage: 100,
|
|
41
|
-
remainingBalance: 500,
|
|
42
|
-
balanceBreakdown: { free: 300, paid: 200 },
|
|
43
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
globalThis.fetch = mock(
|
|
47
|
-
async () =>
|
|
48
|
-
new Response(JSON.stringify(mockResponse), {
|
|
49
|
-
status: 200,
|
|
50
|
-
headers: { 'Content-Type': 'application/json' },
|
|
51
|
-
}),
|
|
52
|
-
) as unknown as typeof fetch
|
|
53
|
-
|
|
54
|
-
const result = await fetchUsageData({ authToken: 'test-token' })
|
|
55
|
-
|
|
56
|
-
expect(result).toEqual(mockResponse)
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
test('should handle full balance breakdown with all fields', async () => {
|
|
60
|
-
const mockResponse = {
|
|
61
|
-
type: 'usage-response' as const,
|
|
62
|
-
usage: 250,
|
|
63
|
-
remainingBalance: 1000,
|
|
64
|
-
balanceBreakdown: {
|
|
65
|
-
free: 100,
|
|
66
|
-
paid: 500,
|
|
67
|
-
ad: 200,
|
|
68
|
-
referral: 150,
|
|
69
|
-
admin: 50,
|
|
70
|
-
},
|
|
71
|
-
next_quota_reset: '2024-03-01T00:00:00.000Z',
|
|
72
|
-
autoTopupEnabled: true,
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
globalThis.fetch = mock(
|
|
76
|
-
async () =>
|
|
77
|
-
new Response(JSON.stringify(mockResponse), {
|
|
78
|
-
status: 200,
|
|
79
|
-
headers: { 'Content-Type': 'application/json' },
|
|
80
|
-
}),
|
|
81
|
-
) as unknown as typeof fetch
|
|
82
|
-
|
|
83
|
-
const result = await fetchUsageData({ authToken: 'test-token' })
|
|
84
|
-
|
|
85
|
-
expect(result).toEqual(mockResponse)
|
|
86
|
-
expect(result.balanceBreakdown?.ad).toBe(200)
|
|
87
|
-
expect(result.balanceBreakdown?.referral).toBe(150)
|
|
88
|
-
expect(result.balanceBreakdown?.admin).toBe(50)
|
|
89
|
-
expect(result.autoTopupEnabled).toBe(true)
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test('should handle null remaining balance', async () => {
|
|
93
|
-
const mockResponse = {
|
|
94
|
-
type: 'usage-response' as const,
|
|
95
|
-
usage: 0,
|
|
96
|
-
remainingBalance: null,
|
|
97
|
-
next_quota_reset: null,
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
globalThis.fetch = mock(
|
|
101
|
-
async () =>
|
|
102
|
-
new Response(JSON.stringify(mockResponse), {
|
|
103
|
-
status: 200,
|
|
104
|
-
headers: { 'Content-Type': 'application/json' },
|
|
105
|
-
}),
|
|
106
|
-
) as unknown as typeof fetch
|
|
107
|
-
|
|
108
|
-
const result = await fetchUsageData({ authToken: 'test-token' })
|
|
109
|
-
|
|
110
|
-
expect(result.remainingBalance).toBeNull()
|
|
111
|
-
expect(result.next_quota_reset).toBeNull()
|
|
112
|
-
expect(result.balanceBreakdown).toBeUndefined()
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
test('should handle zero usage and balance', async () => {
|
|
116
|
-
const mockResponse = {
|
|
117
|
-
type: 'usage-response' as const,
|
|
118
|
-
usage: 0,
|
|
119
|
-
remainingBalance: 0,
|
|
120
|
-
balanceBreakdown: { free: 0, paid: 0 },
|
|
121
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
globalThis.fetch = mock(
|
|
125
|
-
async () =>
|
|
126
|
-
new Response(JSON.stringify(mockResponse), {
|
|
127
|
-
status: 200,
|
|
128
|
-
headers: { 'Content-Type': 'application/json' },
|
|
129
|
-
}),
|
|
130
|
-
) as unknown as typeof fetch
|
|
131
|
-
|
|
132
|
-
const result = await fetchUsageData({ authToken: 'test-token' })
|
|
133
|
-
|
|
134
|
-
expect(result.usage).toBe(0)
|
|
135
|
-
expect(result.remainingBalance).toBe(0)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('should throw error on failed request', async () => {
|
|
139
|
-
globalThis.fetch = mock(
|
|
140
|
-
async () => new Response('Error', { status: 500 }),
|
|
141
|
-
) as unknown as typeof fetch
|
|
142
|
-
const mockLogger = createMockLogger()
|
|
143
|
-
|
|
144
|
-
await expect(
|
|
145
|
-
fetchUsageData({ authToken: 'test-token', logger: mockLogger }),
|
|
146
|
-
).rejects.toThrow('Failed to fetch usage: 500')
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
test('should throw error on 401 unauthorized', async () => {
|
|
150
|
-
globalThis.fetch = mock(
|
|
151
|
-
async () => new Response('Unauthorized', { status: 401 }),
|
|
152
|
-
) as unknown as typeof fetch
|
|
153
|
-
const mockLogger = createMockLogger()
|
|
154
|
-
|
|
155
|
-
await expect(
|
|
156
|
-
fetchUsageData({ authToken: 'invalid-token', logger: mockLogger }),
|
|
157
|
-
).rejects.toThrow('Failed to fetch usage: 401')
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
test('should throw error on 402 payment required', async () => {
|
|
161
|
-
globalThis.fetch = mock(
|
|
162
|
-
async () => new Response('Payment Required', { status: 402 }),
|
|
163
|
-
) as unknown as typeof fetch
|
|
164
|
-
const mockLogger = createMockLogger()
|
|
165
|
-
|
|
166
|
-
await expect(
|
|
167
|
-
fetchUsageData({ authToken: 'test-token', logger: mockLogger }),
|
|
168
|
-
).rejects.toThrow('Failed to fetch usage: 402')
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
test('should throw error when app URL is not set', async () => {
|
|
172
|
-
await expect(
|
|
173
|
-
fetchUsageData({
|
|
174
|
-
authToken: 'test-token',
|
|
175
|
-
clientEnv: {
|
|
176
|
-
NEXT_PUBLIC_ANVIL_APP_URL: undefined,
|
|
177
|
-
} as unknown as ClientEnv,
|
|
178
|
-
}),
|
|
179
|
-
).rejects.toThrow('NEXT_PUBLIC_ANVIL_APP_URL is not set')
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
test('should send correct request body', async () => {
|
|
183
|
-
let capturedBody: string | undefined
|
|
184
|
-
|
|
185
|
-
globalThis.fetch = mock(async (url: string, init?: RequestInit) => {
|
|
186
|
-
capturedBody = init?.body as string
|
|
187
|
-
return new Response(
|
|
188
|
-
JSON.stringify({
|
|
189
|
-
type: 'usage-response',
|
|
190
|
-
usage: 0,
|
|
191
|
-
remainingBalance: 100,
|
|
192
|
-
next_quota_reset: null,
|
|
193
|
-
}),
|
|
194
|
-
{ status: 200 },
|
|
195
|
-
)
|
|
196
|
-
}) as unknown as typeof fetch
|
|
197
|
-
|
|
198
|
-
await fetchUsageData({ authToken: 'my-auth-token' })
|
|
199
|
-
|
|
200
|
-
expect(capturedBody).toBeDefined()
|
|
201
|
-
const parsed = JSON.parse(capturedBody!)
|
|
202
|
-
expect(parsed.fingerprintId).toBe('cli-usage')
|
|
203
|
-
expect(parsed.authToken).toBe('my-auth-token')
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
test('should call correct API endpoint', async () => {
|
|
207
|
-
let capturedUrl: string | undefined
|
|
208
|
-
|
|
209
|
-
globalThis.fetch = mock(async (url: string) => {
|
|
210
|
-
capturedUrl = url
|
|
211
|
-
return new Response(
|
|
212
|
-
JSON.stringify({
|
|
213
|
-
type: 'usage-response',
|
|
214
|
-
usage: 0,
|
|
215
|
-
remainingBalance: 100,
|
|
216
|
-
next_quota_reset: null,
|
|
217
|
-
}),
|
|
218
|
-
{ status: 200 },
|
|
219
|
-
)
|
|
220
|
-
}) as unknown as typeof fetch
|
|
221
|
-
|
|
222
|
-
await fetchUsageData({
|
|
223
|
-
authToken: 'test-token',
|
|
224
|
-
clientEnv: {
|
|
225
|
-
NEXT_PUBLIC_ANVIL_APP_URL: 'https://test.codebuff.local',
|
|
226
|
-
} as ClientEnv,
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
expect(capturedUrl).toBe('https://test.codebuff.local/api/v1/usage')
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
test('should log error on failed request', async () => {
|
|
233
|
-
globalThis.fetch = mock(
|
|
234
|
-
async () => new Response('Server Error', { status: 503 }),
|
|
235
|
-
) as unknown as typeof fetch
|
|
236
|
-
|
|
237
|
-
const mockLogger = createMockLogger()
|
|
238
|
-
|
|
239
|
-
await expect(
|
|
240
|
-
fetchUsageData({ authToken: 'test-token', logger: mockLogger }),
|
|
241
|
-
).rejects.toThrow()
|
|
242
|
-
|
|
243
|
-
expect(mockLogger.error).toHaveBeenCalledWith(
|
|
244
|
-
{ status: 503 },
|
|
245
|
-
'Failed to fetch usage data from API',
|
|
246
|
-
)
|
|
247
|
-
})
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
describe('usageQueryKeys', () => {
|
|
251
|
-
test('all returns base query key', () => {
|
|
252
|
-
expect(usageQueryKeys.all).toEqual(['usage'])
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
test('current returns extended query key', () => {
|
|
256
|
-
expect(usageQueryKeys.current()).toEqual(['usage', 'current'])
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
test('current returns new array instance each call', () => {
|
|
260
|
-
const first = usageQueryKeys.current()
|
|
261
|
-
const second = usageQueryKeys.current()
|
|
262
|
-
expect(first).not.toBe(second)
|
|
263
|
-
expect(first).toEqual(second)
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
test('query keys can be used for cache operations', () => {
|
|
267
|
-
const mockData = {
|
|
268
|
-
type: 'usage-response' as const,
|
|
269
|
-
usage: 50,
|
|
270
|
-
remainingBalance: 200,
|
|
271
|
-
next_quota_reset: null,
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
275
|
-
expect(
|
|
276
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
277
|
-
).toEqual(mockData)
|
|
278
|
-
})
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
describe('useRefreshUsage behavior', () => {
|
|
282
|
-
// Note: useRefreshUsage is a React hook that wraps invalidateActivityQuery.
|
|
283
|
-
// We can't call it directly outside a component, but we can test the
|
|
284
|
-
// underlying invalidation behavior it uses.
|
|
285
|
-
|
|
286
|
-
afterEach(() => {
|
|
287
|
-
mock.restore()
|
|
288
|
-
resetActivityQueryCache()
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
test('invalidating usage query preserves cached data', () => {
|
|
292
|
-
const mockData = {
|
|
293
|
-
type: 'usage-response' as const,
|
|
294
|
-
usage: 100,
|
|
295
|
-
remainingBalance: 500,
|
|
296
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Pre-populate cache
|
|
300
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
301
|
-
expect(
|
|
302
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
303
|
-
).toEqual(mockData)
|
|
304
|
-
|
|
305
|
-
// Call the underlying invalidation function (what useRefreshUsage wraps)
|
|
306
|
-
invalidateActivityQuery(usageQueryKeys.current())
|
|
307
|
-
|
|
308
|
-
// Data should still exist (invalidation doesn't remove data)
|
|
309
|
-
expect(
|
|
310
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
311
|
-
).toEqual(mockData)
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
test('invalidation marks data as stale for refetching', () => {
|
|
315
|
-
const mockData = {
|
|
316
|
-
type: 'usage-response' as const,
|
|
317
|
-
usage: 200,
|
|
318
|
-
remainingBalance: 300,
|
|
319
|
-
next_quota_reset: '2024-03-01T00:00:00.000Z',
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
323
|
-
invalidateActivityQuery(usageQueryKeys.current())
|
|
324
|
-
|
|
325
|
-
// Data is still accessible (stale but usable)
|
|
326
|
-
const cached = getActivityQueryData<typeof mockData>(
|
|
327
|
-
usageQueryKeys.current(),
|
|
328
|
-
)
|
|
329
|
-
expect(cached?.usage).toBe(200)
|
|
330
|
-
expect(cached?.remainingBalance).toBe(300)
|
|
331
|
-
})
|
|
332
|
-
})
|
|
333
|
-
|
|
334
|
-
describe('usage query cache behavior', () => {
|
|
335
|
-
afterEach(() => {
|
|
336
|
-
mock.restore()
|
|
337
|
-
resetActivityQueryCache()
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
test('should store and retrieve usage data from cache', () => {
|
|
341
|
-
const mockData = {
|
|
342
|
-
type: 'usage-response' as const,
|
|
343
|
-
usage: 100,
|
|
344
|
-
remainingBalance: 500,
|
|
345
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
349
|
-
expect(
|
|
350
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
351
|
-
).toEqual(mockData)
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
test('should update cache when new data is set', () => {
|
|
355
|
-
const initialData = {
|
|
356
|
-
type: 'usage-response' as const,
|
|
357
|
-
usage: 100,
|
|
358
|
-
remainingBalance: 500,
|
|
359
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const updatedData = {
|
|
363
|
-
type: 'usage-response' as const,
|
|
364
|
-
usage: 150,
|
|
365
|
-
remainingBalance: 450,
|
|
366
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
setActivityQueryData(usageQueryKeys.current(), initialData)
|
|
370
|
-
expect(
|
|
371
|
-
getActivityQueryData<typeof initialData>(usageQueryKeys.current())?.usage,
|
|
372
|
-
).toBe(100)
|
|
373
|
-
|
|
374
|
-
setActivityQueryData(usageQueryKeys.current(), updatedData)
|
|
375
|
-
expect(
|
|
376
|
-
getActivityQueryData<typeof initialData>(usageQueryKeys.current())?.usage,
|
|
377
|
-
).toBe(150)
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
test('should preserve data after invalidation', () => {
|
|
381
|
-
const mockData = {
|
|
382
|
-
type: 'usage-response' as const,
|
|
383
|
-
usage: 100,
|
|
384
|
-
remainingBalance: 500,
|
|
385
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
389
|
-
invalidateActivityQuery(usageQueryKeys.current())
|
|
390
|
-
|
|
391
|
-
// Data should still be accessible
|
|
392
|
-
const cached = getActivityQueryData<typeof mockData>(
|
|
393
|
-
usageQueryKeys.current(),
|
|
394
|
-
)
|
|
395
|
-
expect(cached).toEqual(mockData)
|
|
396
|
-
})
|
|
397
|
-
|
|
398
|
-
test('should handle cache removal', () => {
|
|
399
|
-
const mockData = {
|
|
400
|
-
type: 'usage-response' as const,
|
|
401
|
-
usage: 100,
|
|
402
|
-
remainingBalance: 500,
|
|
403
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
407
|
-
expect(
|
|
408
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
409
|
-
).toBeDefined()
|
|
410
|
-
|
|
411
|
-
removeActivityQuery(usageQueryKeys.current())
|
|
412
|
-
expect(
|
|
413
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
414
|
-
).toBeUndefined()
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
test('should handle balance breakdown with all credit types', () => {
|
|
418
|
-
const mockData = {
|
|
419
|
-
type: 'usage-response' as const,
|
|
420
|
-
usage: 500,
|
|
421
|
-
remainingBalance: 1500,
|
|
422
|
-
balanceBreakdown: {
|
|
423
|
-
free: 300,
|
|
424
|
-
paid: 700,
|
|
425
|
-
ad: 200,
|
|
426
|
-
referral: 200,
|
|
427
|
-
admin: 100,
|
|
428
|
-
},
|
|
429
|
-
next_quota_reset: '2024-02-15T00:00:00.000Z',
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
433
|
-
const cached = getActivityQueryData<typeof mockData>(
|
|
434
|
-
usageQueryKeys.current(),
|
|
435
|
-
)
|
|
436
|
-
|
|
437
|
-
expect(cached?.balanceBreakdown?.free).toBe(300)
|
|
438
|
-
expect(cached?.balanceBreakdown?.paid).toBe(700)
|
|
439
|
-
expect(cached?.balanceBreakdown?.ad).toBe(200)
|
|
440
|
-
expect(cached?.balanceBreakdown?.referral).toBe(200)
|
|
441
|
-
expect(cached?.balanceBreakdown?.admin).toBe(100)
|
|
442
|
-
})
|
|
443
|
-
|
|
444
|
-
test('should handle zero and null values correctly', () => {
|
|
445
|
-
const mockData = {
|
|
446
|
-
type: 'usage-response' as const,
|
|
447
|
-
usage: 0,
|
|
448
|
-
remainingBalance: 0,
|
|
449
|
-
balanceBreakdown: { free: 0, paid: 0 },
|
|
450
|
-
next_quota_reset: null,
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
454
|
-
const cached = getActivityQueryData<typeof mockData>(
|
|
455
|
-
usageQueryKeys.current(),
|
|
456
|
-
)
|
|
457
|
-
|
|
458
|
-
expect(cached?.usage).toBe(0)
|
|
459
|
-
expect(cached?.remainingBalance).toBe(0)
|
|
460
|
-
expect(cached?.next_quota_reset).toBeNull()
|
|
461
|
-
})
|
|
462
|
-
|
|
463
|
-
test('reset clears usage cache', () => {
|
|
464
|
-
const mockData = {
|
|
465
|
-
type: 'usage-response' as const,
|
|
466
|
-
usage: 100,
|
|
467
|
-
remainingBalance: 500,
|
|
468
|
-
next_quota_reset: null,
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
472
|
-
expect(
|
|
473
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
474
|
-
).toBeDefined()
|
|
475
|
-
|
|
476
|
-
resetActivityQueryCache()
|
|
477
|
-
expect(
|
|
478
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
479
|
-
).toBeUndefined()
|
|
480
|
-
})
|
|
481
|
-
|
|
482
|
-
test('multiple invalidations preserve data', () => {
|
|
483
|
-
const mockData = {
|
|
484
|
-
type: 'usage-response' as const,
|
|
485
|
-
usage: 100,
|
|
486
|
-
remainingBalance: 500,
|
|
487
|
-
next_quota_reset: '2024-02-01T00:00:00.000Z',
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
setActivityQueryData(usageQueryKeys.current(), mockData)
|
|
491
|
-
|
|
492
|
-
// Invalidate multiple times
|
|
493
|
-
invalidateActivityQuery(usageQueryKeys.current())
|
|
494
|
-
invalidateActivityQuery(usageQueryKeys.current())
|
|
495
|
-
invalidateActivityQuery(usageQueryKeys.current())
|
|
496
|
-
|
|
497
|
-
// Data should still be there
|
|
498
|
-
expect(
|
|
499
|
-
getActivityQueryData<typeof mockData>(usageQueryKeys.current()),
|
|
500
|
-
).toEqual(mockData)
|
|
501
|
-
})
|
|
502
|
-
})
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
describe,
|
|
3
|
-
test,
|
|
4
|
-
expect,
|
|
5
|
-
mock,
|
|
6
|
-
beforeEach,
|
|
7
|
-
afterEach,
|
|
8
|
-
spyOn,
|
|
9
|
-
} from 'bun:test'
|
|
10
|
-
|
|
11
|
-
import { createMockApiClient } from '../../__tests__/helpers/mock-api-client'
|
|
12
|
-
import * as AnvilApiModule from '../../utils/anvil-api'
|
|
13
|
-
import { fetchUserDetails } from '../use-user-details-query'
|
|
14
|
-
|
|
15
|
-
import type { Logger } from '@anvil/common/types/contracts/logger'
|
|
16
|
-
|
|
17
|
-
describe('fetchUserDetails', () => {
|
|
18
|
-
const mockLogger: Logger = {
|
|
19
|
-
error: mock(() => {}),
|
|
20
|
-
warn: mock(() => {}),
|
|
21
|
-
info: mock(() => {}),
|
|
22
|
-
debug: mock(() => {}),
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const originalEnv = process.env.NEXT_PUBLIC_ANVIL_APP_URL
|
|
26
|
-
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
process.env.NEXT_PUBLIC_ANVIL_APP_URL = 'https://test.codebuff.com'
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
process.env.NEXT_PUBLIC_ANVIL_APP_URL = originalEnv
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
describe('API failure handling', () => {
|
|
36
|
-
test('throws error on 401 Unauthorized response', async () => {
|
|
37
|
-
const meMock = mock(() =>
|
|
38
|
-
Promise.resolve({
|
|
39
|
-
ok: false,
|
|
40
|
-
status: 401,
|
|
41
|
-
}),
|
|
42
|
-
)
|
|
43
|
-
const apiClient = createMockApiClient({ me: meMock })
|
|
44
|
-
|
|
45
|
-
await expect(
|
|
46
|
-
fetchUserDetails({
|
|
47
|
-
authToken: 'invalid-token',
|
|
48
|
-
fields: ['email'] as const,
|
|
49
|
-
logger: mockLogger,
|
|
50
|
-
apiClient,
|
|
51
|
-
}),
|
|
52
|
-
).rejects.toThrow('Failed to fetch user details (HTTP 401)')
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
test('throws error on 500 Internal Server Error response', async () => {
|
|
56
|
-
const meMock = mock(() =>
|
|
57
|
-
Promise.resolve({
|
|
58
|
-
ok: false,
|
|
59
|
-
status: 500,
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
62
|
-
const apiClient = createMockApiClient({ me: meMock })
|
|
63
|
-
|
|
64
|
-
await expect(
|
|
65
|
-
fetchUserDetails({
|
|
66
|
-
authToken: 'valid-token',
|
|
67
|
-
fields: ['email'] as const,
|
|
68
|
-
logger: mockLogger,
|
|
69
|
-
apiClient,
|
|
70
|
-
}),
|
|
71
|
-
).rejects.toThrow('Failed to fetch user details (HTTP 500)')
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('throws error on 403 Forbidden response', async () => {
|
|
75
|
-
const meMock = mock(() =>
|
|
76
|
-
Promise.resolve({
|
|
77
|
-
ok: false,
|
|
78
|
-
status: 403,
|
|
79
|
-
}),
|
|
80
|
-
)
|
|
81
|
-
const apiClient = createMockApiClient({ me: meMock })
|
|
82
|
-
|
|
83
|
-
await expect(
|
|
84
|
-
fetchUserDetails({
|
|
85
|
-
authToken: 'valid-token',
|
|
86
|
-
fields: ['email'] as const,
|
|
87
|
-
logger: mockLogger,
|
|
88
|
-
apiClient,
|
|
89
|
-
}),
|
|
90
|
-
).rejects.toThrow('Failed to fetch user details (HTTP 403)')
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
test('throws error on 404 Not Found response', async () => {
|
|
94
|
-
const meMock = mock(() =>
|
|
95
|
-
Promise.resolve({
|
|
96
|
-
ok: false,
|
|
97
|
-
status: 404,
|
|
98
|
-
}),
|
|
99
|
-
)
|
|
100
|
-
const apiClient = createMockApiClient({ me: meMock })
|
|
101
|
-
|
|
102
|
-
await expect(
|
|
103
|
-
fetchUserDetails({
|
|
104
|
-
authToken: 'valid-token',
|
|
105
|
-
fields: ['id', 'email'] as const,
|
|
106
|
-
logger: mockLogger,
|
|
107
|
-
apiClient,
|
|
108
|
-
}),
|
|
109
|
-
).rejects.toThrow('Failed to fetch user details (HTTP 404)')
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test('logs error before throwing on API failure', async () => {
|
|
113
|
-
const errorSpy = mock(() => {})
|
|
114
|
-
const testLogger: Logger = {
|
|
115
|
-
...mockLogger,
|
|
116
|
-
error: errorSpy,
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const meMock = mock(() =>
|
|
120
|
-
Promise.resolve({
|
|
121
|
-
ok: false,
|
|
122
|
-
status: 500,
|
|
123
|
-
}),
|
|
124
|
-
)
|
|
125
|
-
const apiClient = createMockApiClient({ me: meMock })
|
|
126
|
-
|
|
127
|
-
await expect(
|
|
128
|
-
fetchUserDetails({
|
|
129
|
-
authToken: 'valid-token',
|
|
130
|
-
fields: ['email'] as const,
|
|
131
|
-
logger: testLogger,
|
|
132
|
-
apiClient,
|
|
133
|
-
}),
|
|
134
|
-
).rejects.toThrow()
|
|
135
|
-
|
|
136
|
-
expect(errorSpy).toHaveBeenCalled()
|
|
137
|
-
})
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
describe('successful responses', () => {
|
|
141
|
-
test('returns user details on successful response', async () => {
|
|
142
|
-
const mockUserDetails = {
|
|
143
|
-
email: 'test@example.com',
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const meMock = mock(() =>
|
|
147
|
-
Promise.resolve({
|
|
148
|
-
ok: true,
|
|
149
|
-
status: 200,
|
|
150
|
-
data: mockUserDetails,
|
|
151
|
-
}),
|
|
152
|
-
)
|
|
153
|
-
const apiClient = createMockApiClient({ me: meMock })
|
|
154
|
-
|
|
155
|
-
const result = await fetchUserDetails({
|
|
156
|
-
authToken: 'valid-token',
|
|
157
|
-
fields: ['email'] as const,
|
|
158
|
-
logger: mockLogger,
|
|
159
|
-
apiClient,
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
expect(result).toEqual(mockUserDetails)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
describe('environment validation', () => {
|
|
168
|
-
test('uses shared API client when apiClient is not provided', async () => {
|
|
169
|
-
const meMock = mock(() =>
|
|
170
|
-
Promise.resolve({
|
|
171
|
-
ok: true,
|
|
172
|
-
status: 200,
|
|
173
|
-
data: { email: 'test@example.com' },
|
|
174
|
-
}),
|
|
175
|
-
)
|
|
176
|
-
const apiClient = createMockApiClient({ me: meMock })
|
|
177
|
-
|
|
178
|
-
const setTokenSpy = spyOn(
|
|
179
|
-
AnvilApiModule,
|
|
180
|
-
'setApiClientAuthToken',
|
|
181
|
-
)
|
|
182
|
-
spyOn(AnvilApiModule, 'getApiClient').mockReturnValue(apiClient as ReturnType<typeof AnvilApiModule.getApiClient>)
|
|
183
|
-
|
|
184
|
-
await expect(
|
|
185
|
-
fetchUserDetails({
|
|
186
|
-
authToken: 'valid-token',
|
|
187
|
-
fields: ['email'] as const,
|
|
188
|
-
logger: mockLogger,
|
|
189
|
-
}),
|
|
190
|
-
).resolves.toEqual({ email: 'test@example.com' })
|
|
191
|
-
|
|
192
|
-
expect(setTokenSpy).toHaveBeenCalledWith('valid-token')
|
|
193
|
-
})
|
|
194
|
-
})
|
|
195
|
-
})
|