Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ export class SyncTokens {
return;
}

const newTokens = syncTokenHeaderValue.split(",").map(parseSyncToken);
const newTokens = syncTokenHeaderValue
.split(",")
.map((s) => s.trim())
.map(parseSyncToken);

for (const newToken of newTokens) {
const existingToken = this._currentSyncTokens.get(newToken.id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ describe("http request related tests", () => {
splitAndSort(syncTokens.getSyncTokenHeaderValue()),
);

// multiple tokens with spaces after commas (as HTTP may join duplicate headers)
syncTokens.addSyncTokenFromHeaderValue(undefined); // reset
syncTokens.addSyncTokenFromHeaderValue("x=val1;sn=1, y=val2;sn=2");
assert.equal(splitAndSort(syncTokens.getSyncTokenHeaderValue()), "x=val1,y=val2");

// and if we get back undefined (ie, the header wasn't there) then it
// resets the entire thing
// (sync tokens are temporary in nature and expire as things are committed
Expand Down Expand Up @@ -232,6 +237,60 @@ describe("http request related tests", () => {
assert.equal(syncTokens.getSyncTokenHeaderValue(), "listRevisions=value");
});

it("handles multiple sync tokens in a single response header", async () => {
let capturedSyncTokenHeader: string | undefined;
let callCount = 0;

client = createMockSyncTokenClient(syncTokens, async (request: PipelineRequest) => {
callCount++;
if (callCount === 1) {
return {
headers: createHttpHeaders({
"sync-token": "tokenA=valueA;sn=1,tokenB=valueB;sn=2,tokenC=valueC;sn=3",
}),
status: 200,
request,
};
}
capturedSyncTokenHeader = request.headers.get("sync-token");
return {
headers: createHttpHeaders(),
status: 200,
request,
};
});

await client.setConfigurationSetting({ key: "key1" });

const headerValue = syncTokens.getSyncTokenHeaderValue()!;
const tokens = headerValue.split(",").sort();
assert.equal(tokens.length, 3);
assert.deepEqual(tokens, ["tokenA=valueA", "tokenB=valueB", "tokenC=valueC"]);

await client.getConfigurationSetting({ key: "key2" });
const sentTokens = capturedSyncTokenHeader!.split(",").sort();
assert.deepEqual(sentTokens, ["tokenA=valueA", "tokenB=valueB", "tokenC=valueC"]);
});

it("handles sync tokens with spaces after commas (HTTP joined headers)", async () => {
client = createMockSyncTokenClient(syncTokens, async (request: PipelineRequest) => {
return {
headers: createHttpHeaders({
"sync-token": "tokenA=valueA;sn=1, tokenB=valueB;sn=2",
}),
status: 200,
request,
};
});

await client.setConfigurationSetting({ key: "key1" });

const headerValue = syncTokens.getSyncTokenHeaderValue()!;
const tokens = headerValue.split(",").sort();
assert.equal(tokens.length, 2);
assert.deepEqual(tokens, ["tokenA=valueA", "tokenB=valueB"]);
});

it("setReadOnly (clear and set)", async () => {
client = createMockSyncTokenClient(syncTokens, async (request: PipelineRequest) => {
return {
Expand Down