Skip to content

Commit

Permalink
wip test against 20k URLs
Browse files Browse the repository at this point in the history
  • Loading branch information
Lms24 committed Oct 18, 2024
1 parent 65e9d4b commit d6c3f1d
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 56 deletions.
2 changes: 2 additions & 0 deletions api-node/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,5 @@ pids

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

.urls.json
7 changes: 5 additions & 2 deletions api-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"test:e2e": "jest --config ./test/jest-e2e.json --testPathIgnorePatterns=everything.e2e-spec.ts",
"test:e2e:urls": "NODE_ENV=test jest --config ./test/jest-e2e.json everything.e2e-spec.ts",
"test:e2e:urls:generate": "ts-node ./test/utils/getUrls.ts",
"python-api:build": "docker build -t registry-api-server ../",
"python-api:start": "docker run -d -p 5031:5030 registry-api-server",
"python-api:stop": "docker stop $(docker ps -q --filter ancestor=registry-api-server)",
Expand All @@ -33,7 +35,8 @@
"@sentry/nestjs": "^8.34.0",
"cache-manager": "^5.7.6",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1"
"rxjs": "^7.8.1",
"semver": "^7.6.3"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",
Expand Down
37 changes: 19 additions & 18 deletions api-node/src/common/registry.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,9 @@ import type {
MarketingSlugs,
} from '../marketing/types';
import type { AppEntry, Apps } from '../apps/types';
import type {
PackageEntry,
Packages,
PackageVersions,
} from '../packages/types';
import type { PackageEntry, Packages } from '../packages/types';
import type { AwsLambdaLayers } from '../aws-lambda-layers/types';
import * as semver from 'semver';

const SDKS_PATH = path.join('..', 'sdks');
const APPS_PATH = path.join('..', 'apps');
Expand Down Expand Up @@ -79,13 +76,13 @@ export class RegistryService {

getSdkVersions(sdkId: string): SdkVersions {
const latest = this.getSdk(sdkId);
const { versions } = this.getPackageVersions(latest.canonical);
const versions = this.getPackageVersions(latest.canonical);
return { latest, versions };
}

// Packages

getPackages(): Packages {
getPackages(strict: boolean = false): Packages {

Check failure on line 85 in api-node/src/common/registry.service.ts

View workflow job for this annotation

GitHub Actions / Lint

'strict' is assigned a value but never used
return this.#packages.reduce((acc, canonical) => {
const packageDir = getPackageDirFromCanonical(canonical);
const latestFilePath = path.join(packageDir, 'latest.json');
Expand All @@ -105,7 +102,7 @@ export class RegistryService {
}, {});
}

getPackageVersions(packageName: string): PackageVersions {
getPackageVersions(packageName: string): string[] | null {
const packageDir = getPackageDirFromCanonical(packageName);
try {
const versions = fs
Expand All @@ -118,27 +115,31 @@ export class RegistryService {
return versionFile.version;
});

const dedupedVersions = Array.from(new Set(versions));

const latest = JSON.parse(
fs.readFileSync(path.join(packageDir, 'latest.json')).toString(),
);

return { versions: dedupedVersions, latest };
// dedupe and sort versions
return Array.from(new Set(versions)).sort((a, b) => {
return semver.parse(a).compare(semver.parse(b));
});
} catch (e) {
console.error(`Failed to read package versions: ${packageName}`);
console.error(e);
return [];
}
}

getPackage(packageName: string, version: string = 'latest'): PackageEntry {
getPackage(
packageName: string,
version: string = 'latest',
): PackageEntry | null {
try {
const packageDir = getPackageDirFromCanonical(packageName);
const versionFilePath = path.join(packageDir, `${version}.json`);
return JSON.parse(fs.readFileSync(versionFilePath).toString());
} catch (e) {
console.error(`Failed to read package by version: ${packageName}`);
console.error(
`Failed to read package ${packageName} for version ${version}`,
);
console.error(e);
return null;
}
}

Expand Down Expand Up @@ -177,7 +178,7 @@ export class RegistryService {
// Marketing

getMarketingSlugs(): MarketingSlugs {
return { slugs: Object.keys(this.#slugs) };
return { slugs: Object.keys(this.#slugs).sort() };
}

resolveMarketingSlug(slug: string): ResolvedMarketingSlug | null {
Expand Down
3 changes: 3 additions & 0 deletions api-node/src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function isTruthy(value: string): boolean {
return value.toLowerCase() === 'true' || value === '1' || value === 'yes';
}
1 change: 1 addition & 0 deletions api-node/src/instrument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ Sentry.init({
release: packageJson.version,
environment: process.env.NODE_ENV || 'development',
tracesSampleRate: 1.0,
enabled: process.env.NODE_ENV !== 'test',
});
4 changes: 3 additions & 1 deletion api-node/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
const app = await NestFactory.create(AppModule, {
logger: ['debug', 'error', 'fatal', 'verbose', 'log', 'warn'],
});
await app.listen(3000);
}

Expand Down
27 changes: 21 additions & 6 deletions api-node/src/packages/packages.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Controller, Get, Param } from '@nestjs/common';
import {
Controller,
Get,
NotFoundException,
Param,
Query,
} from '@nestjs/common';
import { RegistryService } from '../common/registry.service';
import { PackageEntry, Packages, PackageVersions } from './types';

Expand All @@ -7,20 +13,29 @@ export class PackagesController {
constructor(private registryService: RegistryService) {}

@Get()
getPackages(): Packages {
return this.registryService.getPackages();
getPackages(@Query('strict') strict: boolean = false): Packages {
return this.registryService.getPackages(strict);
}

@Get('/:package(*)/versions')
getPackageVersions(@Param('package') pgkName: string): PackageVersions {
return this.registryService.getPackageVersions(pgkName);
const latest = this.registryService.getPackage(pgkName);
if (!latest) {
throw new NotFoundException();
}
const versions = this.registryService.getPackageVersions(pgkName);
return { versions, latest };
}

@Get('/:package(*)/:version')
getPackageByVersion(
@Param('package') pkgName: string,
@Param('version') version: string,
): PackageEntry {
return this.registryService.getPackage(pkgName, version);
): PackageEntry | null {
const pkg = this.registryService.getPackage(pkgName, version);
if (!pkg) {
throw new NotFoundException();
}
return pkg;
}
}
57 changes: 57 additions & 0 deletions api-node/test/everything.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { PYTHON_API_URL } from './utils';
import * as fs from 'fs';
describe('python and Node api responses match', () => {
const urls = JSON.parse(
fs.readFileSync(`${__dirname}/utils/.urls.json`, 'utf8'),
);

it.each(urls)('%s', async ({ url, status }) => {
let pythonResponse, nodeResponse, pythonResponseStatus, nodeResponseStatus;
let pythonResponseBody, nodeResponseBody;
let attempts = 0;
while (attempts < 3) {
try {
pythonResponse = await fetch(`${PYTHON_API_URL}${url}`, {
redirect: 'manual',
});
nodeResponse = await fetch(`http://localhost:3000${url}`, {
redirect: 'manual',
});
if (pythonResponse?.status < 400 && nodeResponse?.status < 400) {
pythonResponseStatus = pythonResponse?.status;
nodeResponseStatus = nodeResponse?.status;
pythonResponseBody = await pythonResponse?.text();
nodeResponseBody = await nodeResponse?.text();
break;
}
} catch (error) {
console.error(`Attempt ${attempts + 1} failed:`, error);
}
attempts++;
if (attempts < 3) {
pythonResponseStatus = pythonResponse?.status;
nodeResponseStatus = nodeResponse?.status;
pythonResponseBody = await pythonResponse?.text();
nodeResponseBody = await nodeResponse?.text();
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second before retrying
}
}

if (attempts === 3) {
console.error(`Failed to fetch ${url} after 3 attempts`);
}

expect(pythonResponseStatus).toBe(status);
expect(nodeResponseStatus).toBe(pythonResponseStatus);

if (pythonResponseStatus === 200) {
try {
const pythonResponseJson = JSON.parse(pythonResponseBody);
const nodeResponseJson = JSON.parse(nodeResponseBody);
expect(nodeResponseJson).toEqual(pythonResponseJson);
} catch {
expect(nodeResponseBody).toEqual(pythonResponseBody);
}
}
});
});
2 changes: 1 addition & 1 deletion api-node/test/marketing.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('MarketingController (e2e)', () => {
.get('/marketing-slugs')
.expect((res) => {
expect(res.status).toBe(200);
expect(res.body.slugs.sort()).toEqual(pythonApiData.slugs.sort());
expect(res.body.slugs).toEqual(pythonApiData.slugs);
});
});

Expand Down
85 changes: 57 additions & 28 deletions api-node/test/packages.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,39 +29,68 @@ describe('PackagesController (e2e)', () => {
});
});

it('/packages/:packageName/versions (GET)', async () => {
const packageName = 'npm:@sentry/angular';
describe('/packages/:packageName/versions (GET)', () => {
it('returns versions for existing package', async () => {
const packageName = 'npm:@sentry/angular';

const pythonApiResponse = await fetch(
`${PYTHON_API_URL}/packages/${packageName}/versions`,
);
const pythonApiData = await pythonApiResponse.json();
const pythonApiResponse = await fetch(
`${PYTHON_API_URL}/packages/${packageName}/versions`,
);
const pythonApiData = await pythonApiResponse.json();

return request(app.getHttpServer())
.get(`/packages/${packageName}/versions`)
.expect((r) => {
expect(r.status).toEqual(200);
const { versions, latest } = r.body;
expect(versions.length).toEqual(pythonApiData.versions.length);
expect(versions.sort()).toEqual(pythonApiData.versions.sort());
expect(latest).toEqual(pythonApiData.latest);
});
return request(app.getHttpServer())
.get(`/packages/${packageName}/versions`)
.expect((r) => {
expect(r.status).toEqual(200);
expect(r.body).toEqual(pythonApiData);
});
});

it('returns 404 for non-existing package', async () => {
const nonExistingPackage = 'npm:@sentry/non-existing-package';

const pythonApiResponse = await fetch(
`${PYTHON_API_URL}/packages/${nonExistingPackage}/versions`,
);
expect(pythonApiResponse.status).toEqual(404);

return request(app.getHttpServer())
.get(`/packages/${nonExistingPackage}/versions`)
.expect(404);
});
});

it('/packages/:packageName/:version (GET)', async () => {
const packageName = 'npm:@sentry/angular';
const version = '8.0.0';
describe('/packages/:packageName/:version (GET)', () => {
it('returns package info for existing package', async () => {
const packageName = 'npm:@sentry/angular';
const version = '8.0.0';

const pythonApiResponse = await fetch(
`${PYTHON_API_URL}/packages/${packageName}/${version}`,
);
const pythonApiData = await pythonApiResponse.json();
const pythonApiResponse = await fetch(
`${PYTHON_API_URL}/packages/${packageName}/${version}`,
);
const pythonApiData = await pythonApiResponse.json();

return request(app.getHttpServer())
.get(`/packages/${packageName}/${version}`)
.expect((r) => {
expect(r.status).toEqual(200);
expect(r.body).toEqual(pythonApiData);
});
return request(app.getHttpServer())
.get(`/packages/${packageName}/${version}`)
.expect((r) => {
expect(r.status).toEqual(200);
expect(r.body).toEqual(pythonApiData);
});
});

it('returns 404 for non-existent package', async () => {
const nonExistentPackage = 'npm:@sentry/non-existent-package';
const version = 'latest';

const pythonApiResponse = await fetch(
`${PYTHON_API_URL}/packages/${nonExistentPackage}/${version}`,
);

expect(pythonApiResponse.status).toEqual(404);

return request(app.getHttpServer())
.get(`/packages/${nonExistentPackage}/${version}`)
.expect(404);
});
});
});
3 changes: 3 additions & 0 deletions api-node/test/utils.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
const PYTHON_API_PORT = 5031;
export const PYTHON_API_URL = `http://localhost:${PYTHON_API_PORT}`;

import * as fs from 'fs';

Check failure on line 4 in api-node/test/utils.ts

View workflow job for this annotation

GitHub Actions / Lint

'fs' is defined but never used
import * as path from 'path';

Check failure on line 5 in api-node/test/utils.ts

View workflow job for this annotation

GitHub Actions / Lint

'path' is defined but never used
Loading

0 comments on commit d6c3f1d

Please sign in to comment.