1
+ // test/suite/api.test.ts
2
+ import * as assert from 'assert' ;
3
+ import * as sinon from 'sinon' ;
4
+ import { askClaude , ClaudeResponse } from '../../src/api' ;
5
+ import * as vscode from 'vscode' ;
6
+
7
+ suite ( 'Claude API Tests' , ( ) => {
8
+ let sandbox : sinon . SinonSandbox ;
9
+ let fetchStub : sinon . SinonStub ;
10
+
11
+ const mockConfig = {
12
+ apiKey : 'test-key' ,
13
+ model : 'claude-3-opus-20240229'
14
+ } ;
15
+
16
+ const mockSuccessResponse : ClaudeResponse = {
17
+ id : 'test-id' ,
18
+ type : 'message' ,
19
+ role : 'assistant' ,
20
+ content : [ { type : 'text' , text : 'Test response' } ] ,
21
+ model : 'claude-3-opus-20240229' ,
22
+ stop_reason : null ,
23
+ stop_sequence : null ,
24
+ usage : {
25
+ input_tokens : 10 ,
26
+ output_tokens : 20
27
+ }
28
+ } ;
29
+
30
+ setup ( ( ) => {
31
+ sandbox = sinon . createSandbox ( ) ;
32
+
33
+ // Stub global fetch
34
+ fetchStub = sandbox . stub ( global , 'fetch' ) ;
35
+
36
+ // Stub configuration
37
+ sandbox . stub ( vscode . workspace , 'getConfiguration' ) . returns ( {
38
+ get : ( key : string ) => {
39
+ if ( key === 'apiKey' ) return mockConfig . apiKey ;
40
+ if ( key === 'model' ) return mockConfig . model ;
41
+ return undefined ;
42
+ }
43
+ } as any ) ;
44
+ } ) ;
45
+
46
+ teardown ( ( ) => {
47
+ sandbox . restore ( ) ;
48
+ } ) ;
49
+
50
+ test ( 'successful API call' , async ( ) => {
51
+ fetchStub . resolves ( {
52
+ ok : true ,
53
+ json : async ( ) => mockSuccessResponse
54
+ } as Response ) ;
55
+
56
+ const response = await askClaude ( 'Test prompt' ) ;
57
+
58
+ assert . strictEqual ( response . content [ 0 ] . text , 'Test response' ) ;
59
+ assert . strictEqual ( response . model , 'claude-3-opus-20240229' ) ;
60
+
61
+ const fetchCall = fetchStub . getCall ( 0 ) ;
62
+ const requestInit = fetchCall . args [ 1 ] as RequestInit & {
63
+ headers : Record < string , string > ;
64
+ } ;
65
+ const requestBody = JSON . parse ( requestInit . body as string ) ;
66
+
67
+ assert . deepStrictEqual ( requestBody . messages , [ {
68
+ role : 'user' ,
69
+ content : 'Test prompt'
70
+ } ] ) ;
71
+ assert . strictEqual ( requestInit . headers [ 'anthropic-version' ] , '2023-06-01' ) ;
72
+ } ) ;
73
+
74
+ test ( 'validates request headers' , async ( ) => {
75
+ fetchStub . resolves ( {
76
+ ok : true ,
77
+ json : async ( ) => mockSuccessResponse
78
+ } as Response ) ;
79
+
80
+ await askClaude ( 'Test prompt' ) ;
81
+
82
+ const requestInit = fetchStub . getCall ( 0 ) . args [ 1 ] as RequestInit & {
83
+ headers : Record < string , string > ;
84
+ } ;
85
+
86
+ assert . deepStrictEqual ( requestInit . headers , {
87
+ 'Content-Type' : 'application/json' ,
88
+ 'anthropic-version' : '2023-06-01' ,
89
+ 'x-api-key' : 'test-key'
90
+ } ) ;
91
+ } ) ;
92
+
93
+ test ( 'handles request cancellation' , async ( ) => {
94
+ const tokenSource = new vscode . CancellationTokenSource ( ) ;
95
+
96
+ // Create an AbortError that matches the browser's native error
97
+ const abortError = new Error ( ) ;
98
+ abortError . name = 'AbortError' ;
99
+
100
+ // Setup fetch to throw the abort error after a delay
101
+ fetchStub . callsFake ( ( ) => new Promise ( ( _ , reject ) => {
102
+ setTimeout ( ( ) => {
103
+ reject ( abortError ) ;
104
+ } , 10 ) ;
105
+ } ) ) ;
106
+
107
+ // Start the request and immediately cancel
108
+ const promise = askClaude ( 'Test prompt' , tokenSource . token ) ;
109
+ tokenSource . cancel ( ) ;
110
+
111
+ // Use a custom validator function
112
+ await assert . rejects (
113
+ promise ,
114
+ error => {
115
+ // Check if the error is an instance of vscode.CancellationError
116
+ return error instanceof vscode . CancellationError ;
117
+ } ,
118
+ 'Expected a CancellationError'
119
+ ) ;
120
+ } ) ;
121
+
122
+ test ( 'handles missing API key' , async ( ) => {
123
+ // Update config stub to return no API key
124
+ sandbox . restore ( ) ;
125
+ sandbox . stub ( vscode . workspace , 'getConfiguration' ) . returns ( {
126
+ get : ( key : string ) => undefined
127
+ } as any ) ;
128
+ process . env . CLAUDE_API_KEY = '' ;
129
+
130
+ await assert . rejects (
131
+ askClaude ( 'Test prompt' ) ,
132
+ / N o A P I k e y c o n f i g u r e d /
133
+ ) ;
134
+ } ) ;
135
+
136
+ test ( 'validates response format' , async ( ) => {
137
+ fetchStub . resolves ( {
138
+ ok : true ,
139
+ json : async ( ) => ( {
140
+ id : 'test-id' ,
141
+ // Missing required fields
142
+ } )
143
+ } as Response ) ;
144
+
145
+ await assert . rejects (
146
+ askClaude ( 'Test prompt' ) ,
147
+ / I n v a l i d r e s p o n s e f o r m a t /
148
+ ) ;
149
+ } ) ;
150
+
151
+ test ( 'handles network errors' , async ( ) => {
152
+ fetchStub . rejects ( new Error ( 'Network error' ) ) ;
153
+
154
+ await assert . rejects (
155
+ askClaude ( 'Test prompt' ) ,
156
+ / N e t w o r k e r r o r /
157
+ ) ;
158
+ } ) ;
159
+ } ) ;
0 commit comments