mirror of
https://github.com/protomaps/PMTiles.git
synced 2026-02-04 02:41:09 +00:00
* Properly abort requests using AbortController * add basic adapter tests --------- Co-authored-by: Andrew Dassonville <dassonville.andrew@gmail.com>
This commit is contained in:
@@ -215,12 +215,13 @@ export class Protocol {
|
||||
}
|
||||
|
||||
if (this.metadata) {
|
||||
return {
|
||||
data: await instance.getTileJson(params.url),
|
||||
};
|
||||
const data = await instance.getTileJson(params.url);
|
||||
abortController.signal.throwIfAborted();
|
||||
return { data };
|
||||
}
|
||||
|
||||
const h = await instance.getHeader();
|
||||
abortController.signal.throwIfAborted();
|
||||
|
||||
if (h.minLon >= h.maxLon || h.minLat >= h.maxLat) {
|
||||
console.error(
|
||||
@@ -255,6 +256,7 @@ export class Protocol {
|
||||
|
||||
const header = await instance.getHeader();
|
||||
const resp = await instance?.getZxy(+z, +x, +y, abortController.signal);
|
||||
abortController.signal.throwIfAborted();
|
||||
if (resp) {
|
||||
return {
|
||||
data: new Uint8Array(resp.data),
|
||||
|
||||
@@ -355,7 +355,7 @@ export class FetchSource implements Source {
|
||||
let userAgent = "";
|
||||
if ("navigator" in globalThis) {
|
||||
//biome-ignore lint: cf workers
|
||||
userAgent = (globalThis as any).navigator.userAgent || "";
|
||||
userAgent = (globalThis as any).navigator?.userAgent ?? "";
|
||||
}
|
||||
const isWindows = userAgent.indexOf("Windows") > -1;
|
||||
const isChromiumBased = /Chrome|Chromium|Edg|OPR|Brave/.test(userAgent);
|
||||
|
||||
119
js/test/adapter.test.ts
Normal file
119
js/test/adapter.test.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, mock, test } from "node:test";
|
||||
import { PMTiles, Protocol } from "../src";
|
||||
import { mockServer } from "./utils";
|
||||
|
||||
describe("Protocol", () => {
|
||||
test("get TileJSON", async () => {
|
||||
mockServer.reset();
|
||||
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const protocol = new Protocol();
|
||||
protocol.add(pmtiles);
|
||||
|
||||
const result = await protocol.tilev4(
|
||||
{
|
||||
url: "pmtiles://http://localhost:1337/example.pmtiles",
|
||||
type: "json",
|
||||
},
|
||||
new AbortController()
|
||||
);
|
||||
|
||||
assert.deepStrictEqual(result.data, {
|
||||
tiles: ["pmtiles://http://localhost:1337/example.pmtiles/{z}/{x}/{y}"],
|
||||
minzoom: 0,
|
||||
maxzoom: 0,
|
||||
bounds: [0, 0, 0.9999999, 1],
|
||||
});
|
||||
assert.equal(mockServer.numRequests, 1);
|
||||
});
|
||||
|
||||
test("get tile data", async () => {
|
||||
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const protocol = new Protocol();
|
||||
protocol.add(pmtiles);
|
||||
|
||||
const result = await protocol.tilev4(
|
||||
{
|
||||
url: "pmtiles://http://localhost:1337/example.pmtiles/0/0/0",
|
||||
type: "arrayBuffer",
|
||||
},
|
||||
new AbortController()
|
||||
);
|
||||
|
||||
assert.ok(result.data instanceof Uint8Array);
|
||||
assert.strictEqual(result.data.length, 49);
|
||||
});
|
||||
|
||||
test("returns empty data for missing tile if errorOnMissingTile is false", async () => {
|
||||
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const protocol = new Protocol({ errorOnMissingTile: false });
|
||||
protocol.add(pmtiles);
|
||||
|
||||
const result = await protocol.tilev4(
|
||||
{
|
||||
url: "pmtiles://http://localhost:1337/example.pmtiles/25/0/0",
|
||||
type: "arrayBuffer",
|
||||
},
|
||||
new AbortController()
|
||||
);
|
||||
|
||||
assert.ok(result.data instanceof Uint8Array);
|
||||
assert.strictEqual(result.data.length, 0);
|
||||
});
|
||||
|
||||
test("throws error for missing tile if errorOnMissingTile is true", async () => {
|
||||
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const protocol = new Protocol({ errorOnMissingTile: true });
|
||||
protocol.add(pmtiles);
|
||||
|
||||
const promise = protocol.tilev4(
|
||||
{
|
||||
url: "pmtiles://http://localhost:1337/example.pmtiles/25/0/0",
|
||||
type: "arrayBuffer",
|
||||
},
|
||||
new AbortController()
|
||||
);
|
||||
|
||||
assert.rejects(promise, { message: "Tile not found." });
|
||||
});
|
||||
|
||||
test("throws AbortError when AbortController is signaled while accessing TileJSON", async () => {
|
||||
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const protocol = new Protocol();
|
||||
protocol.add(pmtiles);
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const resultPromise = protocol.tilev4(
|
||||
{
|
||||
url: "pmtiles://http://localhost:1337/example.pmtiles",
|
||||
type: "json",
|
||||
},
|
||||
abortController
|
||||
);
|
||||
|
||||
abortController.abort();
|
||||
|
||||
await assert.rejects(resultPromise, { name: "AbortError" });
|
||||
});
|
||||
|
||||
test("throws AbortError when AbortController is signaled while accessing tile data", async () => {
|
||||
const pmtiles = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const protocol = new Protocol();
|
||||
protocol.add(pmtiles);
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
||||
const resultPromise = protocol.tilev4(
|
||||
{
|
||||
url: "pmtiles://http://localhost:1337/example.pmtiles/0/0/0",
|
||||
type: "arrayBuffer",
|
||||
},
|
||||
abortController
|
||||
);
|
||||
|
||||
abortController.abort();
|
||||
|
||||
await assert.rejects(resultPromise, { name: "AbortError" });
|
||||
});
|
||||
});
|
||||
@@ -1 +1,2 @@
|
||||
import "./adapter.test";
|
||||
import "./v3.test";
|
||||
|
||||
44
js/test/utils.ts
Normal file
44
js/test/utils.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import fs from "fs";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
|
||||
class MockServer {
|
||||
etag?: string;
|
||||
numRequests: number;
|
||||
lastCache?: string;
|
||||
|
||||
reset() {
|
||||
this.numRequests = 0;
|
||||
this.etag = undefined;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.numRequests = 0;
|
||||
this.etag = undefined;
|
||||
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
|
||||
const server = setupServer(
|
||||
http.get(
|
||||
"http://localhost:1337/example.pmtiles",
|
||||
({ request, params }) => {
|
||||
this.lastCache = request.cache;
|
||||
this.numRequests++;
|
||||
const range = request.headers.get("range")?.substr(6).split("-");
|
||||
if (!range) {
|
||||
throw new Error("invalid range");
|
||||
}
|
||||
const offset = +range[0];
|
||||
const length = +range[1];
|
||||
const body = serverBuffer.slice(offset, offset + length - 1);
|
||||
return new HttpResponse(body, {
|
||||
status: 206,
|
||||
statusText: "OK",
|
||||
headers: { etag: this.etag } as HeadersInit,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
server.listen({ onUnhandledRequest: "error" });
|
||||
}
|
||||
}
|
||||
|
||||
export const mockServer = new MockServer();
|
||||
@@ -1,8 +1,7 @@
|
||||
import fs from "fs";
|
||||
import assert from "node:assert";
|
||||
import { afterEach, beforeEach, describe, it, test } from "node:test";
|
||||
import { http, HttpResponse } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
import { mockServer } from "./utils";
|
||||
|
||||
import {
|
||||
BufferPosition,
|
||||
@@ -20,47 +19,7 @@ import {
|
||||
zxyToTileId,
|
||||
} from "../src/index";
|
||||
|
||||
class MockServer {
|
||||
etag?: string;
|
||||
numRequests: number;
|
||||
lastCache?: string;
|
||||
|
||||
reset() {
|
||||
this.numRequests = 0;
|
||||
this.etag = undefined;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.numRequests = 0;
|
||||
this.etag = undefined;
|
||||
const serverBuffer = fs.readFileSync("test/data/test_fixture_1.pmtiles");
|
||||
const server = setupServer(
|
||||
http.get(
|
||||
"http://localhost:1337/example.pmtiles",
|
||||
({ request, params }) => {
|
||||
this.lastCache = request.cache;
|
||||
this.numRequests++;
|
||||
const range = request.headers.get("range")?.substr(6).split("-");
|
||||
if (!range) {
|
||||
throw new Error("invalid range");
|
||||
}
|
||||
const offset = +range[0];
|
||||
const length = +range[1];
|
||||
const body = serverBuffer.slice(offset, offset + length - 1);
|
||||
return new HttpResponse(body, {
|
||||
status: 206,
|
||||
statusText: "OK",
|
||||
headers: { etag: this.etag } as HeadersInit,
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
server.listen({ onUnhandledRequest: "error" });
|
||||
}
|
||||
}
|
||||
|
||||
const mockserver = new MockServer();
|
||||
|
||||
describe("pmtiles v3", () => {
|
||||
test("varint", () => {
|
||||
let b: BufferPosition = {
|
||||
buf: new Uint8Array([0, 1, 127, 0xe5, 0x8e, 0x26]),
|
||||
@@ -161,7 +120,9 @@ test("tile search with runlength", () => {
|
||||
});
|
||||
|
||||
test("tile search with multiple tile entries", () => {
|
||||
let entries: Entry[] = [{ tileId: 100, offset: 1, length: 1, runLength: 2 }];
|
||||
let entries: Entry[] = [
|
||||
{ tileId: 100, offset: 1, length: 1, runLength: 2 },
|
||||
];
|
||||
let entry = findTile(entries, 101);
|
||||
assert.strictEqual(entry?.offset, 1);
|
||||
assert.strictEqual(entry?.length, 1);
|
||||
@@ -333,25 +294,26 @@ test("multiple sources in a single cache", async () => {
|
||||
});
|
||||
|
||||
test("etag change", async () => {
|
||||
mockServer.reset();
|
||||
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const tile = await p.getZxy(0, 0, 0);
|
||||
await p.getZxy(0, 0, 0);
|
||||
// header + tile
|
||||
assert.strictEqual(2, mockserver.numRequests);
|
||||
mockserver.etag = "etag_2";
|
||||
assert.strictEqual(2, mockServer.numRequests);
|
||||
mockServer.etag = "etag_2";
|
||||
await p.getZxy(0, 0, 0);
|
||||
// tile + header again + tile
|
||||
assert.strictEqual(5, mockserver.numRequests);
|
||||
assert.strictEqual(5, mockServer.numRequests);
|
||||
});
|
||||
|
||||
test("weak etags", async () => {
|
||||
mockserver.reset();
|
||||
mockServer.reset();
|
||||
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
const tile = await p.getZxy(0, 0, 0);
|
||||
// header + tile
|
||||
assert.strictEqual(2, mockserver.numRequests);
|
||||
mockserver.etag = "W/weak_etag";
|
||||
await p.getZxy(0, 0, 0);
|
||||
assert.strictEqual(3, mockserver.numRequests);
|
||||
// header + tile
|
||||
assert.strictEqual(2, mockServer.numRequests);
|
||||
mockServer.etag = "W/weak_etag";
|
||||
await p.getZxy(0, 0, 0);
|
||||
assert.strictEqual(3, mockServer.numRequests);
|
||||
});
|
||||
|
||||
// handle < 16384 bytes archive case
|
||||
@@ -422,22 +384,25 @@ const originalUserAgent = navigator.userAgent;
|
||||
|
||||
describe("user agent", async () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(globalThis.navigator, "userAgent", {
|
||||
value: "Windows Chrome",
|
||||
Object.defineProperty(global, "navigator", {
|
||||
value: { userAgent: "Windows Chrome" },
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(globalThis.navigator, "userAgent", {
|
||||
value: originalUserAgent,
|
||||
Object.defineProperty(global, "navigator", {
|
||||
value: undefined,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("works around caching bug on chrome on windows", async () => {
|
||||
const p = new PMTiles("http://localhost:1337/example.pmtiles");
|
||||
await p.getZxy(0, 0, 0);
|
||||
assert.equal("no-store", mockserver.lastCache);
|
||||
assert.equal("no-store", mockServer.lastCache);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user