Skip to content

Commit d55fa96

Browse files
committed
Fix User-Agent handling and add comprehensive tests for Initialize-OpenAIAPIRequestParam
1 parent f426115 commit d55fa96

2 files changed

Lines changed: 306 additions & 23 deletions

File tree

Private/Initialize-OpenAIAPIRequestParam.ps1

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -79,43 +79,45 @@ function Initialize-OpenAIAPIRequestParam {
7979
$InternalParams.Headers = $RequestHeaders
8080

8181
# Set UserAgent
82-
if (-not $script:UserAgent) {
83-
$script:UserAgent = Get-UserAgent
82+
if ($RequestHeaders.ContainsKey('User-Agent')) {
83+
$UserAgent = $RequestHeaders.'User-Agent'
8484
}
85-
$InternalParams.UserAgent = $script:UserAgent
85+
elseif (-not $script:UserAgent) {
86+
$UserAgent = Get-UserAgent
87+
$script:UserAgent = $UserAgent
88+
}
89+
$InternalParams.UserAgent = $UserAgent
8690

8791
# Set debug flag
8892
$InternalParams.IsDebug = Test-Debug
8993
if ($InternalParams.IsDebug) {
9094
$InternalParams.Headers['OpenAI-Debug'] = 'true'
9195
}
9296

93-
9497
# Construct Body
9598
if ($null -ne $Body) {
96-
if ($ContentType -match 'multipart/form-data') {
97-
$Boundary = New-MultipartFormBoundary
98-
$Body = New-MultipartFormContent -FormData $Body -Boundary $Boundary
99-
$ContentType = ('multipart/form-data; boundary="{0}"' -f $Boundary)
99+
if ($Body -is [pscustomobject]) {
100+
$Body = ObjectToHashTable $Body
100101
}
101-
elseif ($ContentType -match 'application/json') {
102-
if ($Body -is [pscustomobject]) {
103-
$Body = ObjectToHashTable $Body
104-
}
105-
if ($PSBoundParameters.ContainsKey('AdditionalBody') -and $null -ne $AdditionalBody) {
106-
if ($AdditionalBody -is [string]) {
107-
try {
108-
$AdditionalBody = ConvertFrom-Json $AdditionalBody -Depth 100
109-
}
110-
catch {
111-
Write-Error -Exception ([System.InvalidOperationException]::new('Failed to parse AdditionalBody as JSON.'))
112-
}
102+
if ($PSBoundParameters.ContainsKey('AdditionalBody') -and $null -ne $AdditionalBody) {
103+
if ($AdditionalBody -is [string]) {
104+
try {
105+
$AdditionalBody = ConvertFrom-Json $AdditionalBody -Depth 100
113106
}
114-
if ($AdditionalBody -is [pscustomobject]) {
115-
$AdditionalBody = ObjectToHashTable $AdditionalBody
107+
catch {
108+
Write-Error -Exception ([System.InvalidOperationException]::new('Failed to parse AdditionalBody as JSON.'))
116109
}
117-
$Body = Merge-Dictionary $Body $AdditionalBody
118110
}
111+
if ($AdditionalBody -is [pscustomobject]) {
112+
$AdditionalBody = ObjectToHashTable $AdditionalBody
113+
}
114+
$Body = Merge-Dictionary $Body $AdditionalBody
115+
}
116+
117+
if ($ContentType -match 'multipart/form-data') {
118+
$Boundary = New-MultipartFormBoundary
119+
$Body = New-MultipartFormContent -FormData $Body -Boundary $Boundary
120+
$ContentType = ('multipart/form-data; boundary="{0}"' -f $Boundary)
119121
}
120122
}
121123
$InternalParams.Body = $Body
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
#Requires -Modules @{ ModuleName="Pester"; ModuleVersion="5.3.0" }
2+
3+
$ModuleRoot = Split-Path $PSScriptRoot -Parent
4+
$ModuleName = 'PSOpenAI'
5+
Import-Module (Join-Path $ModuleRoot "$ModuleName.psd1") -Force
6+
7+
BeforeAll {
8+
$script:ModuleRoot = Split-Path $PSScriptRoot -Parent
9+
$script:ModuleName = 'PSOpenAI'
10+
$script:TestData = Join-Path $script:ModuleRoot 'Tests/TestData'
11+
Import-Module (Join-Path $script:ModuleRoot "$script:ModuleName.psd1") -Force
12+
}
13+
14+
AfterAll {
15+
$script:UserAgent = $null
16+
}
17+
18+
Describe 'Initialize-OpenAIAPIRequestParam' {
19+
Context 'Unit tests (offline)' -Tag 'Offline' {
20+
InModuleScope $ModuleName {
21+
22+
It 'Only Uri, should return a hashtable with default values' {
23+
$ret = Initialize-OpenAIAPIRequestParam -Uri 'https://api.openai.example.com/v1/test'
24+
# Should return a hashtable
25+
$ret | Should -BeOfType [hashtable]
26+
# Should contain Uri
27+
$ret['Uri'] | Should -BeOfType [System.Uri]
28+
$ret['Uri'].OriginalString | Should -BeExactly 'https://api.openai.example.com/v1/test'
29+
# Should set default values
30+
$ret['Method'] | Should -BeExactly 'Post'
31+
$ret['ContentType'] | Should -BeExactly 'application/json'
32+
$ret['ServiceName'] | Should -BeExactly 'OpenAI'
33+
}
34+
35+
It 'Set basic parameters' {
36+
$Parameters = @{
37+
Method = 'Post'
38+
Uri = 'https://api.openai.example.com/v1/test'
39+
ContentType = 'application/json'
40+
Body = @{ 'key' = 'value' }
41+
Headers = @{ 'Authorization' = 'Bearer token'; 'X-TEST' = 'true' }
42+
AuthType = 'azure'
43+
}
44+
45+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
46+
$ret | Should -BeOfType [hashtable]
47+
$ret['Uri'] | Should -BeOfType [System.Uri]
48+
$ret['Uri'].OriginalString | Should -BeExactly 'https://api.openai.example.com/v1/test'
49+
$ret['Method'] | Should -BeExactly 'Post'
50+
$ret['ContentType'] | Should -BeExactly 'application/json'
51+
$ret['Body'] | Should -BeOfType [hashtable]
52+
$ret['Body']['key'] | Should -BeExactly 'value'
53+
$ret['Headers'] | Should -BeOfType [hashtable]
54+
$ret['Headers']['Authorization'] | Should -BeExactly 'Bearer token'
55+
$ret['Headers']['X-TEST'] | Should -BeExactly 'true'
56+
$ret['ServiceName'] | Should -BeExactly 'Azure OpenAI'
57+
}
58+
59+
It 'Should assert deprecation model' {
60+
Mock -Verifiable -ModuleName $script:ModuleName Assert-DeprecationModel { Write-Warning 'Mock called' }
61+
$Parameters = @{
62+
Uri = 'https://api.openai.example.com/v1/test'
63+
Body = @{ model = 'gpt-3.5-turbo-0301'; 'key' = 'value' }
64+
}
65+
{ $null = Initialize-OpenAIAPIRequestParam @Parameters -WarningAction Stop } | Should -Throw '*Mock called*'
66+
$ret = Initialize-OpenAIAPIRequestParam @Parameters -WarningAction SilentlyContinue
67+
Should -Invoke Assert-DeprecationModel -ModuleName $script:ModuleName
68+
$ret['Uri'].OriginalString | Should -BeExactly 'https://api.openai.example.com/v1/test'
69+
$ret['Body']['model'] | Should -BeExactly 'gpt-3.5-turbo-0301'
70+
$ret['Body']['key'] | Should -BeExactly 'value'
71+
}
72+
73+
It 'Should set User-Agent - test 1' {
74+
$script:UserAgent = $null
75+
Mock -Verifiable -ModuleName $script:ModuleName Get-UserAgent { 'MyCustomUserAgent/1.0' }
76+
$Parameters = @{
77+
Uri = 'https://api.openai.example.com/v1/test'
78+
}
79+
$ret1 = Initialize-OpenAIAPIRequestParam @Parameters
80+
Should -Invoke Get-UserAgent -ModuleName $script:ModuleName -Times 1 -Exactly
81+
$ret1['UserAgent'] | Should -BeExactly 'MyCustomUserAgent/1.0'
82+
83+
# User-Agent is cached, should not call Get-UserAgent again
84+
$ret2 = Initialize-OpenAIAPIRequestParam @Parameters
85+
Should -Invoke Get-UserAgent -ModuleName $script:ModuleName -Times 1 -Exactly
86+
$ret2['UserAgent'] | Should -BeExactly 'MyCustomUserAgent/1.0'
87+
}
88+
89+
It 'Should set User-Agent - test 2' {
90+
$script:UserAgent = $null
91+
Mock -Verifiable -ModuleName $script:ModuleName Get-UserAgent { 'MyCustomUserAgent/1.0' }
92+
$Parameters = @{
93+
Uri = 'https://api.openai.example.com/v1/test'
94+
Headers = @{'Key' = 'Value' }
95+
AdditionalHeaders = @{ 'User-Agent' = 'MyCustomUserAgent2/2.2.2' }
96+
}
97+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
98+
Should -Not -Invoke Get-UserAgent -ModuleName $script:ModuleName
99+
$ret['UserAgent'] | Should -BeExactly 'MyCustomUserAgent2/2.2.2'
100+
}
101+
102+
It 'Should set debug header' {
103+
Mock -Verifiable -ModuleName $script:ModuleName Test-Debug { $true }
104+
$Parameters = @{
105+
Uri = 'https://api.openai.example.com/v1/test'
106+
}
107+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
108+
Should -Invoke Test-Debug -ModuleName $script:ModuleName
109+
$ret['Headers']['OpenAI-Debug'] | Should -BeExactly 'true'
110+
}
111+
112+
It 'multipart/form-data' {
113+
Mock -Verifiable -ModuleName $script:ModuleName New-MultipartFormBoundary { 'boundary' }
114+
$Parameters = @{
115+
Uri = 'https://api.openai.example.com/v1/test'
116+
ContentType = 'multipart/form-data'
117+
Body = [ordered]@{'Key1' = 'value1'; 'Key2' = 'value2' }
118+
}
119+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
120+
$ret['ContentType'] | Should -BeExactly 'multipart/form-data; boundary="boundary"'
121+
$ret['Body'].GetType().Name | Should -Be 'Byte[]'
122+
$BodyAsString = [System.Text.Encoding]::UTF8.GetString($ret['Body'])
123+
$BodyAsString | Should -BeExactly (@(
124+
'--boundary'
125+
'Content-Disposition: form-data; name="Key1"'
126+
''
127+
'value1'
128+
'--boundary'
129+
'Content-Disposition: form-data; name="Key2"'
130+
''
131+
'value2'
132+
'--boundary--'
133+
) -join "`r`n") # Should use CRLF line endings in multipart/form-data
134+
}
135+
136+
It 'AdditionalQuery parameters - test 1' {
137+
$Parameters = @{
138+
Uri = 'https://api.openai.example.com/v1/test'
139+
AdditionalQuery = @{ 'param1' = 'value1'; 'param2' = 'value2' }
140+
}
141+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
142+
$ret['Uri'].ToString() | Should -BeExactly 'https://api.openai.example.com/v1/test?param1=value1&param2=value2'
143+
}
144+
145+
It 'AdditionalQuery parameters - test 2' {
146+
$Parameters = @{
147+
Uri = 'https://api.openai.example.com/v1/test?existing1=param1&existing2=param2'
148+
AdditionalQuery = @{ 'param1' = 'value1'; 'param2' = 'value2' }
149+
}
150+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
151+
$ret['Uri'].ToString() | Should -BeExactly 'https://api.openai.example.com/v1/test?existing1=param1&existing2=param2&param1=value1&param2=value2'
152+
}
153+
154+
It 'AdditionalHeaders parameters' {
155+
$Parameters = @{
156+
Uri = 'https://api.openai.example.com/v1/test'
157+
Headers = @{ 'Authorization' = 'Bearer token'; 'X-TEST' = 'true' }
158+
AdditionalHeaders = @{ 'X-Custom-Header' = 'CustomValue'; 'X-Another-Header' = 'AnotherValue' }
159+
}
160+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
161+
$ret['Headers']['Authorization'] | Should -BeExactly 'Bearer token'
162+
$ret['Headers']['X-TEST'] | Should -BeExactly 'true'
163+
$ret['Headers']['X-Custom-Header'] | Should -BeExactly 'CustomValue'
164+
$ret['Headers']['X-Another-Header'] | Should -BeExactly 'AnotherValue'
165+
}
166+
167+
It 'AdditionalBody parameters - test 1' {
168+
$Parameters = @{
169+
Uri = 'https://api.openai.example.com/v1/test'
170+
ContentType = 'application/json'
171+
Body = @{ 'key1' = 'value1'; 'key2' = 'value2' }
172+
AdditionalBody = @{ 'key3' = 'value3'; 'key4' = 'value4' }
173+
}
174+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
175+
$ret['ContentType'] | Should -BeExactly 'application/json'
176+
$ret['Body'] | Should -BeOfType [hashtable]
177+
$ret['Body']['key1'] | Should -BeExactly 'value1'
178+
$ret['Body']['key2'] | Should -BeExactly 'value2'
179+
$ret['Body']['key3'] | Should -BeExactly 'value3'
180+
$ret['Body']['key4'] | Should -BeExactly 'value4'
181+
}
182+
183+
It 'AdditionalBody parameters - test 2' {
184+
$Parameters = @{
185+
Uri = 'https://api.openai.example.com/v1/test'
186+
ContentType = 'application/json'
187+
Body = [pscustomobject]@{ 'key1' = 'value1'; 'key2' = 'value2' }
188+
AdditionalBody = [pscustomobject]@{ 'key3' = 'value3'; 'key4' = 'value4' }
189+
}
190+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
191+
$ret['ContentType'] | Should -BeExactly 'application/json'
192+
$ret['Body'] | Should -BeOfType [hashtable]
193+
$ret['Body']['key1'] | Should -BeExactly 'value1'
194+
$ret['Body']['key2'] | Should -BeExactly 'value2'
195+
$ret['Body']['key3'] | Should -BeExactly 'value3'
196+
$ret['Body']['key4'] | Should -BeExactly 'value4'
197+
}
198+
199+
It 'AdditionalBody parameters - test 3' {
200+
$Parameters = @{
201+
Uri = 'https://api.openai.example.com/v1/test'
202+
ContentType = 'application/json'
203+
Body = @{ 'key1' = 'value1'; 'key2' = 'value2' }
204+
AdditionalBody = (ConvertTo-Json -InputObject (@{ 'key3' = 'value3'; 'key4' = 'value4' }) -Compress)
205+
}
206+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
207+
$ret['ContentType'] | Should -BeExactly 'application/json'
208+
$ret['Body'] | Should -BeOfType [hashtable]
209+
$ret['Body']['key1'] | Should -BeExactly 'value1'
210+
$ret['Body']['key2'] | Should -BeExactly 'value2'
211+
$ret['Body']['key3'] | Should -BeExactly 'value3'
212+
$ret['Body']['key4'] | Should -BeExactly 'value4'
213+
}
214+
215+
It 'AdditionalBody parameters - test 4' {
216+
$Parameters = @{
217+
Uri = 'https://api.openai.example.com/v1/test'
218+
ContentType = 'application/json'
219+
Body = @{ 'key1' = $true; 'key2' = 123 }
220+
AdditionalBody = @{ 'key3' = @('A', 'B', 'C'); 'key4' = @{ 'subkey1' = 'value1'; 'subkey2' = 'value2' } }
221+
}
222+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
223+
$ret['ContentType'] | Should -BeExactly 'application/json'
224+
$ret['Body'] | Should -BeOfType [hashtable]
225+
$ret['Body']['key1'] | Should -BeTrue
226+
$ret['Body']['key2'] | Should -Be 123
227+
$ret['Body']['key3'] | Should -HaveCount 3
228+
$ret['Body']['key3'][0] | Should -BeExactly 'A'
229+
$ret['Body']['key3'][1] | Should -BeExactly 'B'
230+
$ret['Body']['key3'][2] | Should -BeExactly 'C'
231+
$ret['Body']['key4'] | Should -BeOfType [hashtable]
232+
$ret['Body']['key4']['subkey1'] | Should -BeExactly 'value1'
233+
$ret['Body']['key4']['subkey2'] | Should -BeExactly 'value2'
234+
}
235+
236+
It 'AdditionalBody parameters - test 5' {
237+
Mock -Verifiable -ModuleName $script:ModuleName New-MultipartFormBoundary { 'boundary' }
238+
$Parameters = @{
239+
Uri = 'https://api.openai.example.com/v1/test'
240+
ContentType = 'multipart/form-data'
241+
Body = @{ 'Key1' = 'value1' }
242+
AdditionalBody = @{ 'Key2' = 'value2' }
243+
}
244+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
245+
$ret['ContentType'] | Should -BeExactly 'multipart/form-data; boundary="boundary"'
246+
$ret['Body'].GetType().Name | Should -Be 'Byte[]'
247+
$BodyAsString = [System.Text.Encoding]::UTF8.GetString($ret['Body'])
248+
$BodyAsString | Should -BeExactly (@(
249+
'--boundary'
250+
'Content-Disposition: form-data; name="Key1"'
251+
''
252+
'value1'
253+
'--boundary'
254+
'Content-Disposition: form-data; name="Key2"'
255+
''
256+
'value2'
257+
'--boundary--'
258+
) -join "`r`n") # Should use CRLF line endings in multipart/form-data
259+
}
260+
261+
It 'Unknown content type' {
262+
$Parameters = @{
263+
Uri = 'https://api.openai.example.com/v1/test'
264+
ContentType = 'application/uknown'
265+
Body = 'Unknown Content'
266+
}
267+
$ret = Initialize-OpenAIAPIRequestParam @Parameters
268+
$ret['ContentType'] | Should -BeExactly 'application/uknown'
269+
$ret['Body'] | Should -BeExactly 'Unknown Content'
270+
}
271+
272+
It 'Accept undefined arguments' {
273+
$Parameters = @{
274+
Uri = 'https://api.openai.example.com/v1/test'
275+
UnknownParam = 'Unknown Value'
276+
}
277+
{ $null = Initialize-OpenAIAPIRequestParam @Parameters } | Should -Not -Throw
278+
}
279+
}
280+
}
281+
}

0 commit comments

Comments
 (0)