fix: update integration tests and exception filter for refactored types
- Update app-distribution tests: 'none' sourceType → 'local' - Handle ApplicationRegistrationException in REST API exception filter so tarball upload errors return proper HTTP status codes - Catch manifest.json read failure in tarball upload and throw INVALID_INPUT (400) instead of PACKAGE_RESOLUTION_FAILED (500) - Update marketplace-catalog-sync test to include marketplaceDisplayData in raw SQL inserts (matching what the real sync cron produces) Made-with: Cursor
This commit is contained in:
+11
-2
@@ -53,12 +53,21 @@ export class AppTarballUploadService {
|
||||
|
||||
const contentDir = await resolvePackageContentDir(extractDir);
|
||||
|
||||
const manifest = await readJsonFile<{
|
||||
let manifest: {
|
||||
application?: {
|
||||
universalIdentifier?: string;
|
||||
displayName?: string;
|
||||
};
|
||||
}>(contentDir, 'manifest.json');
|
||||
};
|
||||
|
||||
try {
|
||||
manifest = await readJsonFile(contentDir, 'manifest.json');
|
||||
} catch {
|
||||
throw new ApplicationRegistrationException(
|
||||
'manifest.json not found or invalid in tarball',
|
||||
ApplicationRegistrationExceptionCode.INVALID_INPUT,
|
||||
);
|
||||
}
|
||||
|
||||
const universalIdentifier =
|
||||
params.universalIdentifier ?? manifest.application?.universalIdentifier;
|
||||
|
||||
+51
-2
@@ -6,22 +6,40 @@ import {
|
||||
|
||||
import { type Response } from 'express';
|
||||
|
||||
import {
|
||||
ApplicationRegistrationException,
|
||||
ApplicationRegistrationExceptionCode,
|
||||
} from 'src/engine/core-modules/application-registration/application-registration.exception';
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
|
||||
@Catch(ApplicationException)
|
||||
@Catch(ApplicationException, ApplicationRegistrationException)
|
||||
export class ApplicationRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
catch(exception: ApplicationException, host: ArgumentsHost) {
|
||||
catch(
|
||||
exception: ApplicationException | ApplicationRegistrationException,
|
||||
host: ArgumentsHost,
|
||||
) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
if (exception instanceof ApplicationRegistrationException) {
|
||||
return this.handleRegistrationException(exception, response);
|
||||
}
|
||||
|
||||
return this.handleApplicationException(exception, response);
|
||||
}
|
||||
|
||||
private handleApplicationException(
|
||||
exception: ApplicationException,
|
||||
response: Response,
|
||||
) {
|
||||
switch (exception.code) {
|
||||
case ApplicationExceptionCode.OBJECT_NOT_FOUND:
|
||||
case ApplicationExceptionCode.FIELD_NOT_FOUND:
|
||||
@@ -60,4 +78,35 @@ export class ApplicationRestApiExceptionFilter implements ExceptionFilter {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private handleRegistrationException(
|
||||
exception: ApplicationRegistrationException,
|
||||
response: Response,
|
||||
) {
|
||||
switch (exception.code) {
|
||||
case ApplicationRegistrationExceptionCode.APPLICATION_REGISTRATION_NOT_FOUND:
|
||||
case ApplicationRegistrationExceptionCode.VARIABLE_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case ApplicationRegistrationExceptionCode.INVALID_INPUT:
|
||||
case ApplicationRegistrationExceptionCode.INVALID_SCOPE:
|
||||
case ApplicationRegistrationExceptionCode.INVALID_REDIRECT_URI:
|
||||
case ApplicationRegistrationExceptionCode.SOURCE_CHANNEL_MISMATCH:
|
||||
case ApplicationRegistrationExceptionCode.UNIVERSAL_IDENTIFIER_ALREADY_CLAIMED:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
400,
|
||||
);
|
||||
default:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -75,7 +75,7 @@ const insertRegistrationWithSource = async (
|
||||
params: {
|
||||
universalIdentifier: string;
|
||||
name: string;
|
||||
sourceType: 'npm' | 'tarball' | 'none';
|
||||
sourceType: 'npm' | 'tarball' | 'local';
|
||||
sourcePackage?: string;
|
||||
},
|
||||
): Promise<string> => {
|
||||
@@ -248,13 +248,13 @@ describe('App Distribution (integration)', () => {
|
||||
expect(res.body.messages[0]).toContain('registered as npm');
|
||||
});
|
||||
|
||||
it('should allow tarball upload for none-sourced registration', async () => {
|
||||
it('should allow tarball upload for local-sourced registration', async () => {
|
||||
const uid = crypto.randomUUID();
|
||||
|
||||
const regId = await insertRegistrationWithSource(ds, {
|
||||
universalIdentifier: uid,
|
||||
name: 'None Source App',
|
||||
sourceType: 'none',
|
||||
name: 'Local Source App',
|
||||
sourceType: 'local',
|
||||
});
|
||||
|
||||
createdRegistrationIds.push(regId);
|
||||
|
||||
+12
-4
@@ -58,6 +58,7 @@ describe('Marketplace Catalog Sync (integration)', () => {
|
||||
name: string;
|
||||
sourcePackage: string;
|
||||
latestAvailableVersion?: string;
|
||||
marketplaceDisplayData?: Record<string, unknown>;
|
||||
}): Promise<string> => {
|
||||
const id = crypto.randomUUID();
|
||||
const oAuthClientId = crypto.randomUUID();
|
||||
@@ -66,8 +67,9 @@ describe('Marketplace Catalog Sync (integration)', () => {
|
||||
`INSERT INTO core."applicationRegistration"
|
||||
(id, "universalIdentifier", name, "oAuthClientId",
|
||||
"oAuthRedirectUris", "oAuthScopes", "workspaceId",
|
||||
"sourceType", "sourcePackage", "latestAvailableVersion")
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
|
||||
"sourceType", "sourcePackage", "latestAvailableVersion",
|
||||
"marketplaceDisplayData")
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
|
||||
[
|
||||
id,
|
||||
params.universalIdentifier,
|
||||
@@ -79,6 +81,9 @@ describe('Marketplace Catalog Sync (integration)', () => {
|
||||
'npm',
|
||||
params.sourcePackage,
|
||||
params.latestAvailableVersion ?? '1.0.0',
|
||||
params.marketplaceDisplayData
|
||||
? JSON.stringify(params.marketplaceDisplayData)
|
||||
: null,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -141,14 +146,17 @@ describe('Marketplace Catalog Sync (integration)', () => {
|
||||
});
|
||||
|
||||
it('should enrich curated apps with rich display data', async () => {
|
||||
// The curated index has a Data Enrichment entry with identifier
|
||||
// a1b2c3d4-0000-0000-0000-000000000001 — create a matching registration
|
||||
const curatedUid = 'a1b2c3d4-0000-0000-0000-000000000001';
|
||||
|
||||
await insertCatalogRegistration({
|
||||
universalIdentifier: curatedUid,
|
||||
name: 'Data Enrichment',
|
||||
sourcePackage: '@twentyhq/app-data-enrichment',
|
||||
marketplaceDisplayData: {
|
||||
icon: 'IconSparkles',
|
||||
version: '1.0.0',
|
||||
category: 'Data',
|
||||
},
|
||||
});
|
||||
|
||||
const res = await gqlRequest(MARKETPLACE_QUERY).expect(200);
|
||||
|
||||
Reference in New Issue
Block a user