UNPKG

@elastic/ems-client

Version:

JavaScript client library for the Elastic Maps Service

502 lines (434 loc) 17.3 kB
/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ import { getEMSClient } from './ems_client_util'; import { FileLayer } from '../src/file_layer'; import { LATEST_API_URL_PATH } from '../src/ems_client'; it('should get api manifests', async () => { const { emsClient, getManifestMock } = getEMSClient({ language: 'zz', tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); await emsClient.getTMSServices(); await emsClient.getFileLayers(); expect(getManifestMock).toHaveBeenNthCalledWith(1, 'https://tiles.foobar/v7.6/manifest'); expect(getManifestMock).toHaveBeenNthCalledWith(2, 'https://files.foobar/v7.6/manifest'); }); it('should handle end slashes in api urls correctly', async () => { const { emsClient, getManifestMock } = getEMSClient({ language: 'zz', tileApiUrl: 'https://tiles.foobar/', fileApiUrl: 'https://files.foobar/', emsVersion: '7.6', }); await emsClient.getTMSServices(); await emsClient.getFileLayers(); expect(getManifestMock).toHaveBeenNthCalledWith(1, 'https://tiles.foobar/v7.6/manifest'); expect(getManifestMock).toHaveBeenNthCalledWith(2, 'https://files.foobar/v7.6/manifest'); }); it('should get the tile service', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const tiles = await emsClient.getTMSServices(); expect(tiles.length).toBe(3); const tileService = tiles[0]; expect(await tileService.getUrlTemplate()).toBe( 'https://tiles.foobar/raster/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(await tileService.getUrlTemplateForVector('openmaptiles')).toBe( 'https://tiles.foobar/data/v3/{z}/{x}/{y}.pbf?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(await tileService.getMinZoom('raster')).toBe(0); expect(await tileService.getMaxZoom('raster')).toBe(10); expect(await tileService.getMinZoom('vector')).toBe(3); expect(await tileService.getMaxZoom('vector')).toBe(15); expect(tileService.hasId('road_map')).toBe(true); }); it('tile service- localized (fallback)', async () => { const { emsClient } = getEMSClient({ language: 'zz', //madeup tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const tiles = await emsClient.getTMSServices(); expect(tiles.length).toBe(3); const tileService = tiles[0]; expect(await tileService.getUrlTemplate()).toBe( 'https://tiles.foobar/raster/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(await tileService.getMinZoom('raster')).toBe(0); expect(await tileService.getMaxZoom('raster')).toBe(10); expect(tileService.hasId('road_map')).toBe(true); }); it('.addQueryParams', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const tilesBefore = await emsClient.getTMSServices(); const urlBefore = await tilesBefore[0].getUrlTemplate(); expect(urlBefore).toBe( 'https://tiles.foobar/raster/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); emsClient.addQueryParams({ foo: 'bar', }); let tiles = await emsClient.getTMSServices(); let url = await tiles[0].getUrlTemplate(); expect(url).toBe( 'https://tiles.foobar/raster/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x&foo=bar' ); emsClient.addQueryParams({ foo: 'schmoo', bar: 'foo', }); tiles = await emsClient.getTMSServices(); url = await tiles[0].getUrlTemplate(); expect(url).toBe( 'https://tiles.foobar/raster/styles/osm-bright/{z}/{x}/{y}.png?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x&foo=schmoo&bar=foo' ); }); it('.getFileLayers', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const layers = await emsClient.getFileLayers(); expect(layers.length).toBe(19); }); it('.getFileLayers with a single format', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const layers = await emsClient.getFileLayers(); const layer = layers[0]; expect(layer.getId()).toBe('world_countries'); expect(layer.hasId('world_countries')).toBe(true); expect(layer.getFields()).toMatchObject([ { type: 'id', id: 'iso2', label: { en: 'ISO 3166-1 alpha-2 code', }, }, { type: 'id', id: 'iso3', label: { en: 'ISO 3166-1 alpha-3 code', }, }, { type: 'property', id: 'name', label: { en: 'name', }, }, ]); expect(layer.getDisplayName()).toBe('World Countries'); expect(layer.getFormatOfTypeUrl('geojson')).toBe( 'https://files.foobar/files/world_countries_v1.geo.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(layer.getFormatOfType('geojson')).toBe('geojson'); expect(layer.getDefaultFormatType()).toBe('geojson'); expect(layer.getFormatOfTypeMeta('geojson')).toBeUndefined(); expect(layer.getFormatOfTypeMeta('topojson')).toBeUndefined(); }); it('Test field metadata', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const layers = await emsClient.getFileLayers(); const france = layers.find((l) => l.getId() === 'france_departments'); expect(france).toBeDefined(); if (france) { const insee = france.getFields().find((f) => f.id === 'insee'); expect(insee).toBeDefined(); if (insee) { expect(insee.alias).toBeDefined(); if (insee.alias && insee.alias.length > 0) { expect(insee.alias[0]).toBe('insee'); } expect(insee.regex).toBeDefined(); expect(insee.regex).toBe('^(\\d{2}|2[AB]|9[78]\\d)D?$'); expect(insee.values).toBeDefined(); if (insee.values && insee.values.length > 0) { expect(insee.values.length).toBe(96); expect(insee.values[0]).toBe('30'); } } } }); it('.getFileLayers with multiple formats', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const layers = await emsClient.getFileLayers(); const layer = layers[1]; expect(layer.getId()).toBe('administrative_regions_lvl2'); expect(layer.hasId('administrative_regions_lvl2')).toBe(true); expect(layer.getFields()).toMatchObject([ { type: 'id', id: 'region_iso_code', label: { en: 'Region ISO code', }, }, { type: 'property', id: 'region_name', label: { en: 'Region name', }, }, ]); expect(layer.getDisplayName()).toBe('Administrative regions'); expect(layer.getFormatOfTypeUrl('geojson')).toBe( 'https://files.foobar/files/admin_regions_lvl2_v2.geo.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(layer.getFormatOfTypeUrl('topojson')).toBe( 'https://files.foobar/files/admin_regions_lvl2_v2.topo.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(layer.getDefaultFormatUrl()).toBe( 'https://files.foobar/files/admin_regions_lvl2_v2.topo.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(layer.getFormatOfType('geojson')).toBe('geojson'); expect(layer.getDefaultFormatType()).toBe('topojson'); expect(layer.getFormatOfTypeMeta('geojson')).toBeUndefined(); expect(layer.getFormatOfTypeMeta('topojson')).toMatchObject({ feature_collection_path: 'data', }); }); it('.getFileLayers[0] - localized (known)', async () => { const { emsClient } = getEMSClient({ language: 'fr', tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); emsClient.addQueryParams({ foo: 'bar', }); const layers = await emsClient.getFileLayers(); const layer = layers[0]; expect(layer.getId()).toBe('world_countries'); expect(layer.hasId('world_countries')).toBe(true); expect(layer.getDisplayName()).toBe('pays'); const fields = layer.getFieldsInLanguage(); expect(fields).toEqual([ { name: 'iso2', description: 'code ISO 3166-1 alpha-2 du pays', type: 'id' }, { name: 'iso3', description: 'code ISO 3166-1 alpha-3', type: 'id' }, { name: 'name', description: 'nom', type: 'property' }, ]); expect(layer.getDefaultFormatType()).toBe('geojson'); expect(layer.getDefaultFormatUrl()).toBe( 'https://files.foobar/files/world_countries_v1.geo.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x&foo=bar' ); expect(layer.getDefaultFormatMeta()).toBeUndefined(); }); it('.getFileLayers[0] - localized (fallback)', async () => { const { emsClient } = getEMSClient({ language: 'zz', //madeup tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const layers = await emsClient.getFileLayers(); const layer = layers[0]; expect(layer.getId()).toBe('world_countries'); expect(layer.hasId('world_countries')).toBe(true); expect(layer.getDisplayName()).toBe('World Countries'); const fields = layer.getFieldsInLanguage(); expect(fields).toEqual([ { name: 'iso2', description: 'ISO 3166-1 alpha-2 code', type: 'id' }, { name: 'iso3', description: 'ISO 3166-1 alpha-3 code', type: 'id' }, { name: 'name', description: 'name', type: 'property' }, ]); expect(await layer.getEMSHotLink()).toBe( 'https://landing.foobar/?locale=zz#file/world_countries' ); }); describe('.getFileLayers[1] - getGeoJson defaults', () => { const { emsClient, getManifestMock } = getEMSClient({ language: 'en', tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.13', }); let layers: FileLayer[], topoLayer: FileLayer, fetchedJson: unknown; beforeAll(async () => { layers = await emsClient.getFileLayers(); topoLayer = layers[1]; fetchedJson = await topoLayer.getGeoJson(); }); it('should convert topojson to geojson', () => { expect(emsClient.getCachedGeoJson(topoLayer.getId())).toMatchSnapshot(); }); it('should store the converted geojson in the cache', () => { expect(getManifestMock).toHaveBeenNthCalledWith(1, 'https://files.foobar/v7.13/manifest'); expect(getManifestMock).toHaveBeenNthCalledWith( 2, 'https://files.foobar/files/admin_regions_lvl2_v2.topo.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); }); it('subsequent requests for the same layer should not call emsClient.getManifest', async () => { const cachedJson = await topoLayer.getGeoJson(); expect(getManifestMock).toHaveBeenCalledTimes(2); expect(fetchedJson).toStrictEqual(cachedJson); }); it('should return a geojson layer and cache the result', async () => { const geoLayer = layers[0]; await geoLayer.getGeoJson(); await geoLayer.getGeoJson(); expect(getManifestMock).toHaveBeenNthCalledWith( 3, 'https://files.foobar/files/world_countries_v1.geo.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(getManifestMock).toHaveBeenCalledTimes(3); }); it('should reset the cache when emsClient is changed', () => { const cacheId = topoLayer.getId(); emsClient.addQueryParams({ foo: 'bar' }); // calls private method _invalidateSettings expect(emsClient.getCachedGeoJson(cacheId)).toBeUndefined(); }); }); it('.getFileLayers[1] - getGeoJson limited cache', async () => { const { emsClient } = getEMSClient({ language: 'en', tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.13', cacheSize: 1, }); const layers = await emsClient.getFileLayers(); for await (const layer of layers.slice(0, 2)) { layer.getGeoJson(); } expect(emsClient.getCachedGeoJson(layers[0].getId())).toBeUndefined(); expect(emsClient.getCachedGeoJson(layers[1].getId())).toMatchSnapshot(); }); it('.findFileLayerById', async () => { const { emsClient } = getEMSClient({ language: 'zz', //madeup tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const layer = await emsClient.findFileLayerById('world_countries'); expect(layer!.getId()).toBe('world_countries'); expect(layer!.hasId('world_countries')).toBe(true); }); it('.findTMSServiceById', async () => { const { emsClient } = getEMSClient({ language: 'zz', //madeup tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const tmsService = await emsClient.findTMSServiceById('road_map'); expect(tmsService!.getId()).toBe('road_map'); }); it('should prepend proxypath', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'http://proxy.com/foobar/tiles', fileApiUrl: 'http://proxy.com/foobar/vector', emsVersion: '7.6', }); //should prepend the proxypath to all urls, for tiles and files const tmsServices = await emsClient.getTMSServices(); expect(tmsServices.length).toBe(1); const urlTemplate = await tmsServices[0].getUrlTemplate(); expect(urlTemplate).toBe( 'http://proxy.com/foobar/tiles/raster/osm_bright/{x}/{y}/{z}.jpg?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); const fileLayers = await emsClient.getFileLayers(); expect(fileLayers.length).toBe(2); const fileLayer = fileLayers[0]; expect(fileLayer.getDefaultFormatUrl()).toBe( 'http://proxy.com/foobar/vector/files/world_countries.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(fileLayer.getFormatOfTypeUrl('geojson')).toBe( 'http://proxy.com/foobar/vector/files/world_countries.json?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); }); it('should retrieve vectorstylesheet with all sources inlined)', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'https://tiles.foobar', fileApiUrl: 'https://files.foobar', emsVersion: '7.6', }); const tmsServices = await emsClient.getTMSServices(); expect(tmsServices.length).toBe(3); const tmsService = tmsServices[0]; const styleSheet = await tmsService.getVectorStyleSheet(); expect(styleSheet!.layers!.length).toBe(111); expect(styleSheet!.sprite).toBe('https://tiles.foobar/styles/osm-bright/sprite'); expect(styleSheet!.sources!.openmaptiles!.tiles!.length).toBe(1); expect(styleSheet!.sources!.openmaptiles!.tiles![0]).toBe( 'https://tiles.foobar/data/v3/{z}/{x}/{y}.pbf?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(styleSheet!.sources!.openmaptiles!.type).toBe('vector'); }); it('should retrieve vectorstylesheet with all sources inlined) (proxy)', async () => { const { emsClient } = getEMSClient({ tileApiUrl: 'http://proxy.com/foobar/tiles', fileApiUrl: 'http://proxy.com/foobar/files', emsVersion: '7.6', }); const tmsServices = await emsClient.getTMSServices(); expect(tmsServices.length).toBe(1); const tmsService = tmsServices[0]; const styleSheet = await tmsService.getVectorStyleSheet(); expect(styleSheet!.layers!.length).toBe(111); expect(styleSheet!.sprite).toBe('http://proxy.com/foobar/tiles/styles/osm-bright/sprite'); expect(styleSheet!.sources!.openmaptiles!.tiles!.length).toBe(1); expect(styleSheet!.sources!.openmaptiles!.tiles![0]).toBe( 'http://proxy.com/foobar/tiles/data/v3/{z}/{x}/{y}.pbf?elastic_tile_service_tos=agree&my_app_name=tester&my_app_version=7.x.x' ); expect(styleSheet!.sources!.openmaptiles!.type).toBe('vector'); }); it('should use LATEST_API_URL_PATH for date based versions (serverless)', async () => { const version = '2023-01-01'; const { emsClient } = getEMSClient({ appVersion: version, emsVersion: version, }); const { services } = await emsClient.getMainManifest(); expect(services.length).toBeGreaterThan(0); ['tms', 'file'].map((type) => { const service = services.find((s) => s.type === type); const manifestUrl = service?.manifest; expect(manifestUrl).toBeDefined(); expect(manifestUrl?.match(LATEST_API_URL_PATH)).toBeTruthy(); }); }); it('should fail for non SemVer or Date based versions (serverless)', async () => { const version = 'foo'; const t = () => { getEMSClient({ appVersion: version, emsVersion: version, }); }; expect(t).toThrow(Error); });