Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lambda-tiler): add smoke test in health endpoint #1308

Merged
merged 14 commits into from
Nov 6, 2020
Merged
Show file tree
Hide file tree
Changes from 13 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
1 change: 1 addition & 0 deletions packages/lambda-tiler/bundle.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
../../scripts/bundle.js package.json
cd dist
cp ../package.json .
cp -r ../static .
# @see https://sharp.pixelplumbing.com/en/stable/install/#aws-lambda
npm install --arch=x64 --platform=linux sharp
3 changes: 3 additions & 0 deletions packages/lambda-tiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@cogeotiff/source-aws": "^2.2.0",
"@linzjs/geojson": "^4.17.0",
"path-to-regexp": "^6.1.0",
"pixelmatch": "^5.1.0",
"sharp": "^0.26.0"
},
"bundle": {
Expand All @@ -42,6 +43,8 @@
"@types/aws-lambda": "^8.10.43",
"@types/express": "^4.17.8",
"@types/node": "^14.11.2",
"@types/pixelmatch": "^5.0.0",
"@types/sharp": "^0.26.0",
"express": "^4.17.1",
"pretty-json-log": "^0.3.1"
}
Expand Down
7 changes: 0 additions & 7 deletions packages/lambda-tiler/src/__test__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,6 @@ o.spec('LambdaXyz index', () => {
});
});

o('should respond to /health', async () => {
const res = await handleRequest(req('/health'));
o(res.status).equals(200);
o(res.statusDescription).equals('ok');
o(res.header('cache-control')).equals('no-store');
});

o('should respond to /ping', async () => {
const res = await handleRequest(req('/ping'));
o(res.status).equals(200);
Expand Down
3 changes: 2 additions & 1 deletion packages/lambda-tiler/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { LambdaContext, LambdaFunction, LambdaHttpResponse, Router } from '@basemaps/lambda';
import { LogConfig } from '@basemaps/shared';
import { Health, Ping, Version } from './routes/api';
import { Ping, Version } from './routes/api';
import { Health } from './routes/health';
import { Tiles } from './routes/tile';

const app = new Router();
Expand Down
76 changes: 76 additions & 0 deletions packages/lambda-tiler/src/routes/__test__/health.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as fs from 'fs';
import o from 'ospec';
import sinon from 'sinon';
import * as Tile from '../tile';
import { getExpectedTileName, Health, TestTiles } from '../health';
import { LambdaContext, LambdaHttpResponse } from '@basemaps/lambda';
import { LogConfig } from '@basemaps/shared';

const ctx: LambdaContext = new LambdaContext(
{
requestContext: null as any,
httpMethod: 'get',
path: '/v1/tiles/health',
blacha marked this conversation as resolved.
Show resolved Hide resolved
body: null,
isBase64Encoded: false,
},
LogConfig.get(),
);

o.spec('health', () => {
o.afterEach(() => {
sinon.restore();
});

o('Should return bad response', async () => {
// Given ... a bad get tile response
const BadResponse = new LambdaHttpResponse(404, 'Can not get Tile Set.');
sinon.stub(Tile, 'tile').resolves(BadResponse);

// When ...
const res = await Health(ctx);

// Then ...
o(res.status).equals(404);
o(res.statusDescription).equals('Can not get Tile Set.');
});

// Prepare mock test tile response based on the static test tiles
const Response1 = new LambdaHttpResponse(200, 'ok');
const testTileName1 = getExpectedTileName(TestTiles[0].projection, TestTiles[0].testTile, TestTiles[0].format);
const testTileFile1 = fs.readFileSync(testTileName1);
Response1.buffer(testTileFile1);

const Response2 = new LambdaHttpResponse(200, 'ok');
const testTileName2 = getExpectedTileName(TestTiles[1].projection, TestTiles[1].testTile, TestTiles[1].format);
const testTileFile2 = fs.readFileSync(testTileName2);
Response2.buffer(testTileFile2);

o('Should good response', async () => {
// Given ... a series good get tile response
const callback = sinon.stub(Tile, 'tile');
callback.onCall(0).resolves(Response1);
callback.onCall(1).resolves(Response2);

// When ...
const res = await Health(ctx);

// Then ...
o(res.status).equals(200);
o(res.statusDescription).equals('ok');
});

o('Should return mis-match tile response', async () => {
// Given ... a bad get tile response for second get tile
const callback = sinon.stub(Tile, 'tile');
callback.onCall(0).resolves(Response1);
callback.onCall(1).resolves(Response1);

// When ...
const res = await Health(ctx);

// Then ...
o(res.status).equals(404);
o(res.statusDescription).equals('Test TileSet not match.');
});
});
87 changes: 87 additions & 0 deletions packages/lambda-tiler/src/routes/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import * as fs from 'fs';
import * as path from 'path';
import Sharp from 'sharp';
import PixelMatch = require('pixelmatch');
import { Epsg, Tile } from '@basemaps/geo';
import { LambdaHttpResponse, LambdaContext, HttpHeader } from '@basemaps/lambda';
import { ImageFormat } from '@basemaps/tiler';
import { tile } from './tile';

export function getExpectedTileName(projection: Epsg, tile: Tile, format: ImageFormat): string {
return path.join(
__dirname,
'..',
'..',
`static/expected_tile_${projection.code}_${tile.x}_${tile.y}_z${tile.z}.${format}`,
);
}

interface TestTile {
projection: Epsg;
buf: null | Buffer;
format: ImageFormat;
testTile: Tile;
}

export const TestTiles: TestTile[] = [
{ projection: Epsg.Google, format: ImageFormat.PNG, testTile: { x: 252, y: 156, z: 8 }, buf: null },
{ projection: Epsg.Nztm2000, format: ImageFormat.PNG, testTile: { x: 153, y: 255, z: 7 }, buf: null },
];

async function getTestBuffer(testTile: TestTile): Promise<Buffer> {
if (Buffer.isBuffer(testTile.buf)) return testTile.buf;
// Initiate test img buffer if not defined
const testTileName = getExpectedTileName(testTile.projection, testTile.testTile, testTile.format);
testTile.buf = await fs.promises.readFile(testTileName);
return testTile.buf;
}

/**
* Health request get health TileSets and validate with test TileSets
* - Valid response from get heath tile request
* - Valid tile get from the request
*
* @throws LambdaHttpResponse for failure health test
*/
export async function Health(req: LambdaContext): Promise<LambdaHttpResponse> {
for (const test of TestTiles) {
const projection = test.projection;
const testTile = test.testTile;
const format = test.format;
const path = `/v1/tiles/health/
${projection.toEpsgString()}
/${testTile.z}
/${testTile.x}
/${testTile.y}
.${format}`;

const ctx: LambdaContext = new LambdaContext(
{
requestContext: null as any,
httpMethod: 'get',
path: path,
body: null,
isBase64Encoded: false,
},
req.log,
);

// Get the parse response tile to raw buffer
const response = await tile(ctx);
if (response.status != 200) return new LambdaHttpResponse(response.status, response.statusDescription);
if (!Buffer.isBuffer(response.body)) throw new LambdaHttpResponse(404, 'Not a Buffer response content.');
const resImgBuffer = await Sharp(response.body).raw().toBuffer();

// Get test tile to compare
const testBuffer = await getTestBuffer(test);
const testImgBuffer = await Sharp(testBuffer).raw().toBuffer();

const missMatchedPixels = PixelMatch(testImgBuffer, resImgBuffer, null, 256, 256);
if (missMatchedPixels) return new LambdaHttpResponse(404, 'Test TileSet not match.');
}

// Return Ok response when all health test passed.
const OkResponse = new LambdaHttpResponse(200, 'ok');
OkResponse.header(HttpHeader.CacheControl, 'no-store');
return OkResponse;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions packages/landing/src/__test__/config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import o from 'ospec';
import { Config } from '../config';

o.spec('Config', () => {
o('should return the same api key', () => {
const keyA = Config.ApiKey;
o(keyA).equals(Config.ApiKey);
});
});
2 changes: 1 addition & 1 deletion packages/landing/src/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getApiKey } from './api';
import { getApiKey } from '@basemaps/shared/build/api';

const currentApiKey: string = getApiKey();
export const Config = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import o from 'ospec';
import { getApiKey, OneDayMs } from '../api';
import { Config } from '../config';
import { ulid, decodeTime, encodeTime } from 'ulid';

declare const global: {
Expand All @@ -23,12 +22,6 @@ o.spec('ApiKey', () => {
const ulidKey = apiKey.slice(1).toUpperCase();
o(decodeTime(ulidKey) > 0).equals(true);
});

o('should return the same api key', () => {
const keyA = Config.ApiKey;
o(keyA).equals(Config.ApiKey);
});

o('should get valid api keys from localStorage', () => {
localStorage.getItem = o.spy((): string => 'foo');
o(getApiKey()).notEquals('foo');
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { Const, Env } from './const';
export { ApiKeyTable, ApiKeyTableRecord } from './aws/api.key.table';
export { LogConfig, LogType } from './log';
export * from './api.path';
export * from './api';
export { V, VNode, VNodeElement, VNodeText } from './vdom';
export { VNodeParser } from './vdom.parse';
export { CompositeError } from './composite.error';
Expand Down