mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
fix: follow-ups from the review of the price, Immich, moving day and import fixes
Saved place price (#2471) - A price above zero that would be left without a currency is refused with a 400, over REST and through update_collection_place alike. A list has no base currency, so such an amount was read in each viewer's own default currency and went into a trip as the trip's. A price sent alone keeps the stored currency, and a zero price or clearing both fields still goes through. The check sits in the service because the MCP tool spreads the contract's shape, where a refine would never arrive. - An older row that holds an amount without a currency shows it as a bare number instead of in the viewer's own currency. - The Hungarian hint uses the informal register of its file, which is now prettier clean. Immich certificate switch (#2475) - The switch trusts one server. Saving a different URL without sending it turns it off; saving the same URL keeps it as before. The wiki says so. Moving day (#2476) - Google Maps and CoMaps are left out only when there is nothing to hand over. A day with a single stop opens it as a pin again, on the desktop and on the phone, as it did before. - Desktop: a moving day whose flight or train has no stations shows no route tools, since there is nothing left to draw. With stations they stay. - Phone: the drive between two stays on a day without stops or bookings shows once at the top instead of again at the bottom. - The export helpers take the carrier flags as one object instead of two booleans in a row. Import card (#2477) - The success and error marks use the theme tokens. - The e2e test names its upload with a made-up booking number. Tests: COLLECTIONS-SVC-132 and 133, COLLECTIONS-E2E-088, update_collection_place with a price and no currency, FE-COMP-COLEXTRAS-013 (011 adjusted), IMMICH-TLS-010, IMMICH-103 extended, FE-PLANNER-DAYPLAN-222 adjusted and 224, FE-MOB-PLTL-051, FE-MOB-PTLM-047, FE-W4BGT-023 and 024 check the mark colours.
This commit is contained in:
@@ -406,6 +406,25 @@ describe('Tool: update_collection_place / set_collection_place_status / rate_col
|
||||
});
|
||||
});
|
||||
|
||||
// The contract takes price and currency as separate fields; the service holds
|
||||
// them together, so an agent cannot leave a bare amount on a place without one.
|
||||
it('refuses a price without a currency on a place that has none', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const col = seedCollection(user.id, 'P');
|
||||
const pid = seedPlace(col, user.id, 'Pergamon');
|
||||
await withHarness(user.id, async (h) => {
|
||||
const bare = await h.client.callTool({ name: 'update_collection_place', arguments: { placeId: pid, price: 14 } });
|
||||
expect(bare.isError).toBe(true);
|
||||
expect(errorText(bare)).toMatch(/currency/);
|
||||
expect(testDb.prepare('SELECT price, currency FROM collection_places WHERE id = ?').get(pid)).toEqual({ price: null, currency: null });
|
||||
|
||||
const paired = parseToolResult(await h.client.callTool({
|
||||
name: 'update_collection_place', arguments: { placeId: pid, price: 14, currency: 'EUR' },
|
||||
})) as { place: { price: number; currency: string } };
|
||||
expect(paired.place).toMatchObject({ price: 14, currency: 'EUR' });
|
||||
});
|
||||
});
|
||||
|
||||
it('describes the price as a rough cost that needs its currency', async () => {
|
||||
const { user } = createUser(testDb);
|
||||
const h = await createMcpHarness({ userId: user.id, withResources: false });
|
||||
|
||||
@@ -379,6 +379,31 @@ describe('status + updatePlace move', () => {
|
||||
expect(renamed).toMatchObject({ name: 'Kunsthaus Zürich', price: 24.5, currency: 'CHF', website: 'https://kunsthaus.example', phone: '+41 44' });
|
||||
});
|
||||
|
||||
// A list has no base currency, so a bare amount would be read in every
|
||||
// viewer's own default currency. The service refuses it for REST and MCP alike.
|
||||
it('COLLECTIONS-SVC-132: a price without a currency on a place that has none is a 400 and changes nothing', async () => {
|
||||
const u = createUser(testDb).user;
|
||||
const col = svc.createCollection(u.id, { name: 'Zurich' });
|
||||
const p = svc.savePlace(u.id, { collection_id: col.id, name: 'Kunsthaus' }).place!;
|
||||
|
||||
await expect(svc.updatePlace(u.id, p.id, { price: 14 })).rejects.toMatchObject({ status: 400, message: expect.stringMatching(/currency/) });
|
||||
await expect(svc.updatePlace(u.id, p.id, { price: 14, currency: null })).rejects.toMatchObject({ status: 400 });
|
||||
|
||||
expect(testDb.prepare('SELECT price, currency FROM collection_places WHERE id = ?').get(p.id)).toEqual({ price: null, currency: null });
|
||||
});
|
||||
|
||||
it('COLLECTIONS-SVC-133: a price alone keeps the stored currency, a zero price needs none, and a priced place cannot lose its currency', async () => {
|
||||
const u = createUser(testDb).user;
|
||||
const col = svc.createCollection(u.id, { name: 'Zurich' });
|
||||
const priced = svc.savePlace(u.id, { collection_id: col.id, name: 'Kunsthaus', price: 10, currency: 'CHF' }).place!;
|
||||
const free = svc.savePlace(u.id, { collection_id: col.id, name: 'Lake shore' }).place!;
|
||||
|
||||
expect(await svc.updatePlace(u.id, priced.id, { price: 24.5 })).toMatchObject({ price: 24.5, currency: 'CHF' });
|
||||
expect(await svc.updatePlace(u.id, free.id, { price: 0 })).toMatchObject({ price: 0, currency: null });
|
||||
await expect(svc.updatePlace(u.id, priced.id, { currency: null })).rejects.toMatchObject({ status: 400 });
|
||||
expect(await svc.updatePlace(u.id, priced.id, { price: null, currency: null })).toMatchObject({ price: null, currency: null });
|
||||
});
|
||||
|
||||
it('COLLECTIONS-SVC-131: copyToTrip carries the edited price, currency, website and phone into the trip place', async () => {
|
||||
const u = createUser(testDb).user;
|
||||
const trip = createTrip(testDb, u.id);
|
||||
|
||||
@@ -844,6 +844,23 @@ describe('self-signed certificates', () => {
|
||||
expect(row).toEqual({ immich_url: null, immich_allow_insecure_tls: 0 });
|
||||
});
|
||||
|
||||
it('IMMICH-TLS-010: the switch trusts one server, so a new URL saved without it starts off', async () => {
|
||||
seedUser(USER, 'https://nas.local', 'key-1', 0, 1);
|
||||
|
||||
// Same server with a trailing slash: still the same connection, the switch stays.
|
||||
await svc.saveImmichSettings(USER, 'https://nas.local/', 'key-1', null);
|
||||
expect(svc.getConnectionSettings(USER).allow_insecure_tls).toBe(true);
|
||||
|
||||
await svc.saveImmichSettings(USER, 'https://photos.example.com', 'key-2', null);
|
||||
expect(testDb.prepare('SELECT immich_url, immich_allow_insecure_tls FROM users WHERE id = ?').get(USER)).toEqual({
|
||||
immich_url: 'https://photos.example.com', immich_allow_insecure_tls: 0,
|
||||
});
|
||||
|
||||
// Sent along with the new URL, it holds for that server.
|
||||
await svc.saveImmichSettings(USER, 'https://other.example.com', 'key-3', null, true);
|
||||
expect(svc.getConnectionSettings(USER).allow_insecure_tls).toBe(true);
|
||||
});
|
||||
|
||||
it('IMMICH-TLS-006: a URL the guard refuses leaves the stored switch alone', async () => {
|
||||
seedUser(USER, 'https://immich.test', 'key-1', 0, 1);
|
||||
checkSsrf.mockResolvedValue({ allowed: false, error: 'blocked host' });
|
||||
|
||||
Reference in New Issue
Block a user