Skip to content

Commit 99de8a9

Browse files
author
Arun Gopinathan
committed
Fix TypeScript create precondition headers
Use the standard If-None-Match header for tunnel and port creation, preserve caller-owned request options, and cover generated-ID conflict retries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1c4ee497-a9b3-481b-860b-541eef9e9fba
1 parent 42d76f8 commit 99de8a9

2 files changed

Lines changed: 76 additions & 6 deletions

File tree

ts/src/management/tunnelManagementHttpClient.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,8 +349,13 @@ export class TunnelManagementHttpClient implements TunnelManagementClient {
349349
}
350350
}
351351

352-
options.additionalHeaders = options.additionalHeaders || {};
353-
options.additionalHeaders['If-Not-Match'] = "*";
352+
options = {
353+
...options,
354+
additionalHeaders: {
355+
...options.additionalHeaders,
356+
'If-None-Match': '*',
357+
},
358+
};
354359

355360
if (idGenerated) {
356361
tunnel.tunnelId = IdGeneration.generateTunnelId();
@@ -578,9 +583,13 @@ export class TunnelManagementHttpClient implements TunnelManagementClient {
578583
this.raiseReportProgress(TunnelProgress.StartingCreateTunnelPort);
579584
tunnelPort = this.convertTunnelPortForRequest(tunnel, tunnelPort);
580585
const path = `${portsApiSubPath}/${tunnelPort.portNumber}`;
581-
options = options || {};
582-
options.additionalHeaders = options.additionalHeaders || {};
583-
options.additionalHeaders['If-Not-Match'] = "*";
586+
options = {
587+
...options,
588+
additionalHeaders: {
589+
...options?.additionalHeaders,
590+
'If-None-Match': '*',
591+
},
592+
};
584593
const result = (await this.sendTunnelRequest<TunnelPort>(
585594
'PUT',
586595
tunnel,

ts/test/tunnels-test/tunnelManagementTests.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import axios, { Axios, AxiosHeaders, AxiosError, AxiosPromise, AxiosRequestConfi
66
import * as https from 'https';
77
import { suite, test, slow, timeout } from '@testdeck/mocha';
88
import { ManagementApiVersions, TunnelManagementHttpClient } from '@microsoft/dev-tunnels-management';
9-
import { Tunnel, TunnelProgress, TunnelReportProgressEventArgs, ClusterRecommendationResponse, ClusterAvailability } from '@microsoft/dev-tunnels-contracts';
9+
import { Tunnel, TunnelPort, TunnelProgress, TunnelReportProgressEventArgs, ClusterRecommendationResponse, ClusterAvailability } from '@microsoft/dev-tunnels-contracts';
1010
import { CancellationToken, CancellationTokenSource } from 'vscode-jsonrpc';
1111

1212
@suite
@@ -282,6 +282,7 @@ export class TunnelManagementTests {
282282
callCount++;
283283

284284
const sentTunnel = config.data as Tunnel;
285+
assert.strictEqual(config.headers?.['If-None-Match'], '*');
285286
if (callCount === 1) {
286287
firstTunnelId = sentTunnel?.tunnelId;
287288
throw conflictError;
@@ -315,6 +316,66 @@ export class TunnelManagementTests {
315316
}
316317
}
317318

319+
@test
320+
public async createTunnelSendsIfNoneMatchHeader() {
321+
const requestTunnel = <Tunnel>{
322+
tunnelId: 'tunnelid',
323+
clusterId: 'clusterId',
324+
};
325+
this.nextResponse = <Tunnel>{
326+
tunnelId: 'tunnelid',
327+
clusterId: 'clusterId',
328+
};
329+
const options = {
330+
additionalHeaders: {
331+
'X-Test': 'value',
332+
},
333+
};
334+
335+
await this.managementClient.createTunnel(requestTunnel, options);
336+
337+
assert(this.lastRequest);
338+
const headers = this.lastRequest!.config.headers as { [name: string]: string };
339+
assert.strictEqual(headers['If-None-Match'], '*');
340+
assert.strictEqual(headers['If-Not-Match'], undefined);
341+
assert.strictEqual(headers['X-Test'], 'value');
342+
assert.strictEqual(
343+
Object.prototype.hasOwnProperty.call(options.additionalHeaders, 'If-None-Match'),
344+
false,
345+
);
346+
}
347+
348+
@test
349+
public async createTunnelPortSendsIfNoneMatchHeader() {
350+
const requestTunnel = <Tunnel>{
351+
tunnelId: 'tunnelid',
352+
clusterId: 'clusterId',
353+
};
354+
const requestPort = <TunnelPort>{
355+
portNumber: 9900,
356+
};
357+
this.nextResponse = <TunnelPort>{
358+
portNumber: 9900,
359+
};
360+
const options = {
361+
additionalHeaders: {
362+
'X-Test': 'value',
363+
},
364+
};
365+
366+
await this.managementClient.createTunnelPort(requestTunnel, requestPort, options);
367+
368+
assert(this.lastRequest);
369+
const headers = this.lastRequest!.config.headers as { [name: string]: string };
370+
assert.strictEqual(headers['If-None-Match'], '*');
371+
assert.strictEqual(headers['If-Not-Match'], undefined);
372+
assert.strictEqual(headers['X-Test'], 'value');
373+
assert.strictEqual(
374+
Object.prototype.hasOwnProperty.call(options.additionalHeaders, 'If-None-Match'),
375+
false,
376+
);
377+
}
378+
318379
@test
319380
public async getClusterRecommendationsReturnsResponse() {
320381
const response = <ClusterRecommendationResponse>{

0 commit comments

Comments
 (0)