Files
gods-eye-view/src/maps/sourceFactories.test.mjs
T

320 lines
11 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import * as Cesium from 'cesium';
import { createIonImagery } from './imagery.js';
import { createWorldTerrain, createKeylessTerrain } from './terrain.js';
import { createDefaultMapSources } from './defaultSources.js';
test('Esri uses Re:Earth without keys and preserves ion terrain when configured', async () => {
const originalTerrain = Cesium.CesiumTerrainProvider.fromUrl;
const originalResource = Cesium.IonResource.fromAssetId;
const calls = [];
try {
Cesium.CesiumTerrainProvider.fromUrl = async (resource) => {
calls.push(resource);
return { resource };
};
Cesium.IonResource.fromAssetId = async () =>
assert.fail('keyless mode must not acquire ion terrain');
const keyless = createDefaultMapSources();
const esri = keyless.sources.find(
({ descriptor }) => descriptor.id === 'esri-imagery',
);
assert.equal(esri.available, true);
assert.equal(esri.terrain.id, 'keyless');
assert.equal(esri.terrain.create, createKeylessTerrain);
await esri.terrain.create();
// A Resource, not a bare URL: the retry policy rides on it (terrainRetry.js).
assert.equal(calls.length, 1);
assert.ok(calls[0] instanceof Cesium.Resource);
assert.equal(
calls[0].url,
'https://terrain.reearth.land/cesium-mesh/ellipsoid',
);
assert.equal(calls[0].retryAttempts, 3);
assert.equal(typeof calls[0].retryCallback, 'function');
assert.equal(
keyless.sources.find(({ descriptor }) => descriptor.id === 'photoreal')
.available,
false,
);
for (const credentials of [
{ googleApiKey: 'test-key' },
{ cesiumToken: 'test-token' },
]) {
const keyed = createDefaultMapSources({
...credentials,
googleTileset: { show: true },
});
assert.equal(
keyed.sources.find(({ descriptor }) => descriptor.id === 'photoreal')
.available,
true,
);
assert.equal(
keyed.sources.find(({ descriptor }) => descriptor.id === 'esri-imagery')
.terrain.id,
credentials.cesiumToken ? 'world' : 'keyless',
);
const failedTileset = createDefaultMapSources(credentials);
assert.equal(
failedTileset.sources.find(
({ descriptor }) => descriptor.id === 'photoreal',
).available,
false,
);
}
} finally {
Cesium.CesiumTerrainProvider.fromUrl = originalTerrain;
Cesium.IonResource.fromAssetId = originalResource;
}
});
test('imagery and terrain pass their own ion token without relying on SDK defaults', async () => {
const originalImagery = Cesium.IonImageryProvider.fromAssetId;
const originalResource = Cesium.IonResource.fromAssetId;
const originalTerrain = Cesium.CesiumTerrainProvider.fromUrl;
const calls = [];
const defaultToken = Cesium.Ion.defaultAccessToken;
try {
Cesium.IonImageryProvider.fromAssetId = async (id, options) => {
calls.push({ kind: 'imagery', id, options });
return { id };
};
Cesium.IonResource.fromAssetId = async (id, options) => {
calls.push({ kind: 'resource', id, options });
return { id };
};
Cesium.CesiumTerrainProvider.fromUrl = async (resource, options) => {
calls.push({ kind: 'terrain', resource, options });
return { id: 'terrain' };
};
await createIonImagery(12345, 'imagery-token');
const result = await createWorldTerrain('terrain-token');
assert.equal(calls[0].options.accessToken, 'imagery-token');
assert.equal(calls[1].options.accessToken, 'terrain-token');
assert.equal(calls[1].id, 1);
assert.equal(calls[2].options.requestVertexNormals, true);
assert.equal(result.provider.id, 'terrain');
assert.equal(Cesium.Ion.defaultAccessToken, defaultToken);
} finally {
Cesium.IonImageryProvider.fromAssetId = originalImagery;
Cesium.IonResource.fromAssetId = originalResource;
Cesium.CesiumTerrainProvider.fromUrl = originalTerrain;
}
});
test('both Bing ion stacks use HTTPS and retain ion and per-tile attribution', async (t) => {
const defaultToken = Cesium.Ion.defaultAccessToken;
const metadataCalls = [];
const bingCalls = [];
const perTileCalls = [];
const bingCredit = new Cesium.Credit('Bing tile credit', true);
const endpoint = {
type: 'IMAGERY',
externalType: 'BING',
options: Object.freeze({
url: 'https://dev.virtualearth.net',
key: 'ion-issued-bing-fixture',
mapStyle: 'Aerial',
tileProtocol: 'http',
}),
attributions: [
{ html: 'Ion required credit', collapsible: false },
{ html: 'Ion optional credit', collapsible: true },
],
};
t.mock.method(Cesium.Resource.prototype, 'fetchJson', async function () {
metadataCalls.push({
url: this.url,
token: this.queryParameters.access_token,
});
const mapStyle = this.url.includes(
`/assets/${Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS}/`,
)
? 'AerialWithLabels'
: 'Aerial';
return {
...endpoint,
options: Object.freeze({ ...endpoint.options, mapStyle }),
};
});
t.mock.method(Cesium.IonImageryProvider, 'fromAssetId', () =>
assert.fail('Bing must not inherit the local HTTP page protocol'),
);
t.mock.method(
Cesium.BingMapsImageryProvider,
'fromUrl',
async (url, options) => {
bingCalls.push({ url, options });
const provider = {
errorEvent: new Cesium.Event(),
getTileCredits(x, y, level) {
assert.equal(this, provider);
perTileCalls.push([x, y, level]);
return level === 0 ? undefined : [bingCredit];
},
};
return provider;
},
);
for (const style of [
Cesium.IonWorldImageryStyle.AERIAL,
Cesium.IonWorldImageryStyle.AERIAL_WITH_LABELS,
]) {
const provider = await createIonImagery(style, ' own-ion-token ');
const credits = provider.getTileCredits(3, 4, 5);
assert.deepEqual(
credits.map(({ html, showOnScreen }) => ({ html, showOnScreen })),
[
{ html: 'Ion required credit', showOnScreen: true },
{ html: 'Ion optional credit', showOnScreen: false },
{ html: 'Bing tile credit', showOnScreen: true },
],
);
assert.equal(credits[2], bingCredit);
assert.equal(provider.getTileCredits(0, 0, 0).length, 2);
assert.ok(provider.errorEvent instanceof Cesium.Event);
assert.equal(metadataCalls.at(-1).token, 'own-ion-token');
const requested = new URL(metadataCalls.at(-1).url);
assert.equal(requested.origin, 'https://api.cesium.com');
assert.equal(requested.pathname, `/v1/assets/${style}/endpoint`);
}
assert.deepEqual(perTileCalls, [
[3, 4, 5],
[0, 0, 0],
[3, 4, 5],
[0, 0, 0],
]);
assert.equal(bingCalls.length, 2);
for (const [index, { url, options }] of bingCalls.entries()) {
assert.equal(url, 'https://dev.virtualearth.net');
assert.deepEqual(options, {
key: 'ion-issued-bing-fixture',
mapStyle: index === 0 ? 'Aerial' : 'AerialWithLabels',
tileProtocol: 'https',
});
}
assert.equal(endpoint.options.tileProtocol, 'http');
assert.equal(Cesium.Ion.defaultAccessToken, defaultToken);
});
test('Bing ion metadata and construction failures propagate without another source', async (t) => {
const style = Cesium.IonWorldImageryStyle.AERIAL;
const metadataError = new Error('ion metadata fixture failure');
const providerError = new Error('Bing construction fixture failure');
let metadata = () => {
throw metadataError;
};
let bingCalls = 0;
t.mock.method(Cesium.Resource.prototype, 'fetchJson', async () => metadata());
t.mock.method(Cesium.BingMapsImageryProvider, 'fromUrl', async () => {
bingCalls += 1;
throw providerError;
});
t.mock.method(Cesium.IonImageryProvider, 'fromAssetId', () =>
assert.fail('Bing failures must not silently use an alternate provider'),
);
await assert.rejects(createIonImagery(style, 'test-token'), metadataError);
for (const endpoint of [
undefined,
{
type: 'TERRAIN',
externalType: 'BING',
options: { url: 'https://example.test' },
},
{
type: 'IMAGERY',
externalType: 'OTHER',
options: { url: 'https://example.test' },
},
{ type: 'IMAGERY', externalType: 'BING', options: {} },
]) {
metadata = () => endpoint;
await assert.rejects(
createIonImagery(style, 'test-token'),
/Bing imagery endpoint/,
);
}
assert.equal(bingCalls, 0);
metadata = () => ({
type: 'IMAGERY',
externalType: 'BING',
options: { url: 'https://dev.virtualearth.net', key: 'bing-fixture' },
});
await assert.rejects(createIonImagery(style, 'test-token'), providerError);
assert.equal(bingCalls, 1);
});
test('other ion imagery assets keep the standard SDK factory', async (t) => {
const expected = {};
const calls = [];
t.mock.method(Cesium.Resource.prototype, 'fetchJson', () =>
assert.fail('generic assets must use the SDK ion factory'),
);
t.mock.method(Cesium.IonImageryProvider, 'fromAssetId', async (...args) => {
calls.push(args);
return expected;
});
assert.equal(await createIonImagery(12345, ' custom-token '), expected);
assert.deepEqual(calls, [[12345, { accessToken: 'custom-token' }]]);
});
test('cancellation after ion metadata prevents terrain construction', async () => {
const originalResource = Cesium.IonResource.fromAssetId;
const originalTerrain = Cesium.CesiumTerrainProvider.fromUrl;
const controller = new AbortController();
try {
Cesium.IonResource.fromAssetId = async () => {
controller.abort();
return {};
};
Cesium.CesiumTerrainProvider.fromUrl = () =>
assert.fail('cancelled terrain construction');
await assert.rejects(
createWorldTerrain('test-token', { signal: controller.signal }),
{ name: 'AbortError' },
);
} finally {
Cesium.IonResource.fromAssetId = originalResource;
Cesium.CesiumTerrainProvider.fromUrl = originalTerrain;
}
});
test('credentialed source factories reject an omitted token instead of consuming an SDK default', async () => {
assert.throws(
() => createIonImagery(Cesium.IonWorldImageryStyle.AERIAL, ''),
/explicit token/,
);
await assert.rejects(createWorldTerrain(' '), /explicit ion token/);
});
test('both Google 3D routes keep tiles drawing while draped imagery loads', async () => {
const { createGoogleDirectTileset, createGoogleIonTileset } =
await import('./google3d.js');
const calls = [];
const sdk = {
createGooglePhotorealistic3DTileset: async (...args) => {
calls.push(args);
return 'direct';
},
IonResource: { fromAssetId: async (id, options) => ({ id, ...options }) },
Cesium3DTileset: {
fromUrl: async (...args) => {
calls.push(args);
return 'ion';
},
},
};
assert.equal(await createGoogleDirectTileset(sdk, 'key'), 'direct');
assert.equal(await createGoogleIonTileset(sdk, 'token'), 'ion');
assert.deepEqual(calls[0], [
{ key: 'key', onlyUsingWithGoogleGeocoder: true },
{ asynchronouslyLoadImagery: true },
]);
assert.equal(calls[1][0].id, 2275207);
assert.equal(calls[1][0].accessToken, 'token');
assert.equal(calls[1][1].asynchronouslyLoadImagery, true);
assert.equal(calls[1][1].enableCollision, true);
});