Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 5 additions & 7 deletions services/osm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,11 @@ export class OsmApiClient extends BaseHttpClient implements ICancelableClient {
}

async getWorkspaceBbox(workspaceId: WorkspaceId) {
const response = await this._get(`workspaces/${workspaceId}/bbox.json`);

if (response.status === 204) {
return undefined
}

return await response.json();
// The workspace bbox lives on the v1 tasking API, owned by WorkspacesClient.
// OsmApiClient is constructed before workspacesClient (which depends on it),
// so we resolve the singleton lazily at call time to avoid the import cycle.
const { workspacesClient } = await import('~/services/index');
return workspacesClient.getWorkspaceBbox(workspaceId);
}

async getExportBbox(id: number) {
Expand Down
19 changes: 17 additions & 2 deletions services/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,23 @@ export class WorkspacesClient extends BaseHttpClient implements ICancelableClien
}
}

getWorkspaceBbox(id: WorkspaceId): Promise<BoundingBox> {
return this.#osmClient.getWorkspaceBbox(id);
async getWorkspaceBbox(id: WorkspaceId): Promise<BoundingBox | undefined> {
const originalBaseUrl = this._baseUrl;
this._baseUrl = this.#newApiUrl;

try {
const response = await this._send(`workspaces/${id}/bbox`, 'GET');

if (response.status === 204) {
return undefined;
}

// The v1 API returns coordinates already in decimal degrees.
return await response.json();
}
finally {
this._baseUrl = originalBaseUrl;
}
}

async createWorkspace(workspace: WorkspaceCreation): Promise<WorkspaceId> {
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/create-blank.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ async function stubCreateFlow(
route.fulfill({ status: 200, body: '' })
);

// Dashboard map bbox -> 204 so the map init is skipped in headless.
await page.route('**/osm/api/0.6/**/bbox.json', route =>
// Dashboard map bbox (new-API) -> 204 so the map init is skipped in headless.
await page.route('**/workspaces/*/bbox', route =>
route.fulfill({ status: 204, body: '' })
);

Expand Down
13 changes: 7 additions & 6 deletions test/e2e/export-index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { aWorkspace, projectGroups } from '../mocks/fixtures';
//
// The Download flow goes through workspacesClient.exportWorkspaceArchive(workspace),
// which branches on workspace.type:
// - pathways: osmClient.getWorkspaceData(id) = GET osm/.../workspaces/{id}/bbox.json
// - pathways: osmClient.getWorkspaceData(id) = GET new-api/workspaces/{id}/bbox
// then GET osm/.../map.json?bbox=... -> {elements:[]} -> buildPathwaysCsvArchive
// (pure client-side zip; NO tdei host, NO polling).
// - osw: osm export XML + tdeiClient.convertDataset (4s job polling).
Expand All @@ -36,15 +36,15 @@ async function stubGetWorkspace(page: import('@playwright/test').Page, overrides
});
}

// Stubs the OSM-host calls the pathways download flow makes:
// GET .../workspaces/{id}/bbox.json -> a bbox, then GET .../map.json -> elements.
// Stubs the calls the pathways download flow makes:
// GET new-api/workspaces/{id}/bbox -> a bbox, then GET osm/.../map.json -> elements.
// `gate` (if provided) holds the map.json response open so the loading state can
// be observed before the archive is built.
async function stubPathwaysDownloadOk(
page: import('@playwright/test').Page,
gate?: Promise<void>
) {
await page.route('**/osm/**/workspaces/1/bbox.json', route =>
await page.route('**/workspaces/1/bbox', route =>
route.fulfill({ json: { min_lon: -122.4, min_lat: 47.6, max_lon: -122.3, max_lat: 47.7 } })
);
await page.route('**/osm/**/map.json**', async (route) => {
Expand Down Expand Up @@ -151,8 +151,9 @@ test.describe('workspace export landing page', () => {
await seedAuthenticatedSession(page);
await stubGetWorkspace(page, { type: 'pathways' });

// Fail the first OSM call in the download flow so exportWorkspaceArchive throws.
await page.route('**/osm/**/workspaces/1/bbox.json', route =>
// Fail the first call in the download flow so exportWorkspaceArchive throws.
// The bbox lookup now hits the new API; the map fetch stays on the OSM base.
await page.route('**/workspaces/1/bbox', route =>
route.fulfill({ status: 500, body: 'boom' })
);
await page.route('**/osm/**/map.json**', route =>
Expand Down
13 changes: 7 additions & 6 deletions test/e2e/export-tdei.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { aWorkspace, PROJECT_GROUP_ID } from '../mocks/fixtures';
// so the service `<option>`s shown must match that simulated response.
//
// The upload flow (services/export/tdei.ts, osw branch) runs:
// OSM GET workspaces/{id}/bbox.json (dataset_area not in metadata)
// API GET workspaces/{id}/bbox (dataset_area not in metadata)
// OSM GET map?bbox=... (export workspace xml)
// TDEI POST osw/convert -> jobId; poll TDEI GET jobs?job_id=... until COMPLETED;
// TDEI GET job/download/{jobId}
Expand Down Expand Up @@ -155,9 +155,10 @@ test('submitting with valid values shows a loading state', async ({ page }) => {
await stubPageLoad(page, [eligibleProjectGroup]);
await stubServices(page);

// OSM bbox + xml export so the flow reaches the convert step. The OSM API base
// is http://api.test/osm/api/0.6/, so the globs must include api/0.6/.
await page.route('**/osm/api/0.6/workspaces/1/bbox.json', route =>
// Workspace bbox (new-API, decimal degrees) + OSM xml export so the flow reaches
// the convert step. The OSM map base is http://api.test/osm/api/0.6/, so those
// globs must include api/0.6/; the bbox now lives on the new API.
await page.route('**/workspaces/1/bbox', route =>
route.fulfill({ json: { min_lat: 47.6, min_lon: -122.34, max_lat: 47.62, max_lon: -122.32 } })
);
await page.route('**/osm/api/0.6/map?**', route =>
Expand All @@ -184,7 +185,7 @@ test('a duplicate dataset version shows an error and lets the user change the ve
await stubPageLoad(page, [eligibleProjectGroup]);
await stubServices(page);

await page.route('**/osm/api/0.6/workspaces/1/bbox.json', route =>
await page.route('**/workspaces/1/bbox', route =>
route.fulfill({ json: { min_lat: 47.6, min_lon: -122.34, max_lat: 47.62, max_lon: -122.32 } })
);
await page.route('**/osm/api/0.6/map?**', route =>
Expand Down Expand Up @@ -245,7 +246,7 @@ test('an API error shows an error message and a Try again button that resets the
await stubPageLoad(page, [eligibleProjectGroup]);
await stubServices(page);

await page.route('**/osm/api/0.6/workspaces/1/bbox.json', route =>
await page.route('**/workspaces/1/bbox', route =>
route.fulfill({ json: { min_lat: 47.6, min_lon: -122.34, max_lat: 47.62, max_lon: -122.32 } })
);
await page.route('**/osm/api/0.6/map?**', route =>
Expand Down
30 changes: 30 additions & 0 deletions test/unit/services/workspaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,33 @@ describe('WorkspacesClient.getMyWorkspaces', () => {
await expect(makeClient().getMyWorkspaces()).rejects.toBeInstanceOf(WorkspacesClientError);
});
});

describe('WorkspacesClient.getWorkspaceBbox', () => {
// Use a DISTINCT new-API base so the test proves the call base-swaps onto the
// v1 (tasking) API — if it used the legacy base, the new-API stub wouldn't
// match and the fetch would fail.
const NEW_API_BASE = 'http://new-api.test/';

function makeBboxClient() {
return new WorkspacesClient(TEST_API_BASE, NEW_API_BASE, tdeiClient, osmClient);
}

it('fetches the bbox from the v1 endpoint and returns it unchanged (backend sends decimal degrees)', async () => {
// Regression: coordinates come pre-scaled from the backend, so the client
// must NOT re-scale them — bbox in === bbox out.
const bbox = { min_lat: 47.6, min_lon: -122.34, max_lat: 47.62, max_lon: -122.32 };
server.use(
http.get(`${NEW_API_BASE}workspaces/1/bbox`, () => HttpResponse.json(bbox))
);

await expect(makeBboxClient().getWorkspaceBbox(1)).resolves.toEqual(bbox);
});

it('returns undefined when the workspace has no extent (204)', async () => {
server.use(
http.get(`${NEW_API_BASE}workspaces/1/bbox`, () => new HttpResponse(null, { status: 204 }))
);

await expect(makeBboxClient().getWorkspaceBbox(1)).resolves.toBeUndefined();
});
});
Loading