mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-14 00:01:03 +00:00
fix(mail-ingest): send an uploaded .eml as multipart; publish the booking origin as a contract
- The upload posted its FormData through the client's JSON default, so the form went as JSON and the server saw no file (400). It goes through postMultipart now, like every other upload. - reservation-origin.ts becomes reservation-origin.contract.ts: the booking import sets an origin, and a contract is what another domain may import. - CI: the client's strict type check holds the whole program with strict checks and outgrew the default ~4 GB heap; it gets 8 GB like the server's.
This commit is contained in:
@@ -458,7 +458,10 @@ jobs:
|
||||
# tsconfig.strict.json is the same program with strictNullChecks and
|
||||
# noImplicitAny; its errors per file may not rise above
|
||||
# scripts/strict-baseline.json, and a file without an entry, every new
|
||||
# one included, has to be clean (scripts/strict-lint.mjs).
|
||||
# one included, has to be clean (scripts/strict-lint.mjs). It holds the
|
||||
# whole program with strict checks, past Node's default ~4 GB heap.
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
run: cd client && npm run lint:strict
|
||||
|
||||
- name: Page pattern check
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// FE-MAILINGEST-API-001
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
vi.mock('./client', () => ({
|
||||
default: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
|
||||
postMultipart: vi.fn(async () => ({ duplicate: false })),
|
||||
}));
|
||||
|
||||
import apiClient, { postMultipart } from './client';
|
||||
import { mailIngestApi } from './mailIngest';
|
||||
|
||||
describe('mailIngestApi.upload', () => {
|
||||
it('FE-MAILINGEST-API-001: sends the .eml as a multipart form, never through the JSON post', async () => {
|
||||
const file = new File(['From: a@b.c\r\n\r\nhi'], 'booking.eml', { type: 'message/rfc822' });
|
||||
expect(await mailIngestApi.upload(file)).toEqual({ duplicate: false });
|
||||
const [url, form] = vi.mocked(postMultipart).mock.calls[0];
|
||||
expect(url).toBe('/mail-ingest/upload');
|
||||
expect((form as FormData).get('file')).toBe(file);
|
||||
expect(apiClient.post).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import apiClient from './client';
|
||||
import apiClient, { postMultipart } from './client';
|
||||
|
||||
/**
|
||||
* The booking mail import (server nest/mail-ingest): the person's settings and
|
||||
@@ -43,6 +43,7 @@ export const mailIngestApi = {
|
||||
upload: (file: File): Promise<unknown> => {
|
||||
const form = new FormData();
|
||||
form.append('file', file);
|
||||
return apiClient.post('/mail-ingest/upload', form).then(data);
|
||||
// As multipart: under the client's default JSON content type the form went as JSON, without the file.
|
||||
return postMultipart('/mail-ingest/upload', form);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import { McpToolGuardsService } from '../mcp-shared/mcp-tool-guards.service';
|
||||
import { PlaceOriginsService } from '../places/place-origins.service';
|
||||
import { PlacesService } from '../places/places.service';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { withoutOrigin } from '../reservations/reservation-origin';
|
||||
import { withoutOrigin } from '../reservations/reservation-origin.contract';
|
||||
import { ReservationsService, type CreateAccommodation } from '../reservations/reservations.service';
|
||||
import { IMPORT_SOURCE, importExternalId } from './booking-origin';
|
||||
import { KitineraryExtractorService } from './kitinerary-extractor.service';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReservationOrigin } from './reservation-origin';
|
||||
import type { ReservationOrigin } from './reservation-origin.contract';
|
||||
|
||||
/*
|
||||
* The shapes the reservations service takes in: a booking's endpoints, the
|
||||
|
||||
+6
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
* The origin an imported booking carries, published by the reservations
|
||||
* domain: the importers (booking import, mail import) set it, and every other
|
||||
* write path strips it from what a client sends.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Where an imported booking came from (`import`, `mail`) and its key there,
|
||||
* unique per trip. Both or neither; without them the row is a hand-made
|
||||
@@ -2,7 +2,7 @@ import type { User } from '../../types';
|
||||
import { CurrentUser } from '../auth-core/current-user.decorator';
|
||||
import { JwtAuthGuard } from '../auth-core/jwt-auth.guard';
|
||||
import { RequirePermission, TripAccessGuard } from '../permissions/trip-access.guard';
|
||||
import { withoutOrigin } from './reservation-origin';
|
||||
import { withoutOrigin } from './reservation-origin.contract';
|
||||
import { ReservationWritesService } from './reservation-writes.service';
|
||||
import {
|
||||
ReservationCreateDto,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { PluginController, PluginMethod } from '../../nest-rpc/rpc-kit/decorator
|
||||
import type { PluginRpcContext } from '../../nest-rpc/rpc-kit/types';
|
||||
import { num, schemaMessage } from '../../nest-rpc/rpc-params';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { withoutOrigin } from './reservation-origin';
|
||||
import { withoutOrigin } from './reservation-origin.contract';
|
||||
import { ReservationsService } from './reservations.service';
|
||||
import {
|
||||
reservationCreateRequestSchema,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { originColumns } from './reservation-origin';
|
||||
import { originColumns } from './reservation-origin.contract';
|
||||
import type { CreateAccommodation, CreateReservationData, EndpointInput, ReservationEndpoint, UpdateReservationData } from './reservation-inputs';
|
||||
export type { CreateAccommodation, CreateReservationData, EndpointInput, ReservationEndpoint, UpdateReservationData } from './reservation-inputs';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
|
||||
@@ -33,7 +33,7 @@ import { HttpException } from '@nestjs/common';
|
||||
import { resetTestDb } from '../../helpers/test-db';
|
||||
import { createUser, createTrip, createReservation, createBudgetItem, createPlace, createDay, createDayAccommodation, createDayAssignment, addTripMember } from '../../helpers/factories';
|
||||
import type { PermissionsService } from '../../../src/nest/permissions/permissions.service';
|
||||
import { withoutOrigin } from '../../../src/nest/reservations/reservation-origin';
|
||||
import { withoutOrigin } from '../../../src/nest/reservations/reservation-origin.contract';
|
||||
import { ReservationsService, type UpdateReservationData } from '../../../src/nest/reservations/reservations.service';
|
||||
import { ReservationsReadService } from '../../../src/nest/reservations/reservations-read.service';
|
||||
import type { BudgetService } from '../../../src/nest/budget/budget.service';
|
||||
|
||||
Reference in New Issue
Block a user