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
29 changes: 21 additions & 8 deletions src/controllers/columnMappingController.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { processSpecificationMiddlewares } from '../middleware/common.middleware
import platformApi from '../services/platformApi.js'
import { types } from '../utils/logging.js'
import logger from '../utils/logger.js'
import { MiddlewareError } from '../utils/errors.js'

const LOCK_DETECTED_GEOMETRY_MAPPINGS = true
const GEOMETRY_FIELDS = ['geometry', 'point']
Expand All @@ -14,14 +15,7 @@ class ColumnMappingController extends PageController {
middlewareSetup () {
super.middlewareSetup()
this.use(getRequestDataMiddleware)
this.use(async (req, res, next) => {
const { requestData } = req.locals
const params = requestData.getParams() ?? {}
if (await isStatutoryDataset(params.organisationName, params.dataset)) {
return res.status(404).render('errors/404.html')
}
next()
})
this.use(handleUnavailableColumnMappingRequest)
this.use(updateSessionFromRequestData)
// Populate req.params and dataset, then run specification processing middlewares
this.use(async (req, res, next) => {
Expand Down Expand Up @@ -121,6 +115,25 @@ class ColumnMappingController extends PageController {
}
}

export async function handleUnavailableColumnMappingRequest (req, res, next) {
const { requestData } = req.locals
const params = requestData?.getParams?.() ?? {}
const { organisationName, dataset } = params

if (!organisationName || !dataset) {
return next(new MiddlewareError('Column mapping request params not found', 404))
}

if (await isStatutoryDataset({
organisation: organisationName,
dataset
})) {
return next(new MiddlewareError('Column mapping not found', 404))
}

next()
}

/**
* Build the options object passed to the column-mapping template.
* Returns UI-friendly data including expected mapping rows, selectable uploaded
Expand Down
3 changes: 2 additions & 1 deletion src/utils/redisLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ export async function getProvisionReasonsForDataset ({ organisation, dataset })
return provisionReasons
}

export async function isStatutoryDataset ({ organisation, dataset }) {
export async function isStatutoryDataset (requestParams = {}) {
const { organisation, dataset } = requestParams ?? {}
const provisionReasons = await getProvisionReasonsForDataset({ organisation, dataset })
return provisionReasons.includes('statutory')
}
Expand Down
42 changes: 41 additions & 1 deletion test/unit/columnMappingController.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import {
applySubmittedFieldSelections,
applyDetectedGeometryColumnMapping,
Expand All @@ -9,10 +9,50 @@ import {
buildSelectableColumns,
detectGeometryColumnMapping,
getBracketFields,
handleUnavailableColumnMappingRequest,
validateColumnMapping
} from '../../src/controllers/columnMappingController.js'
import { MiddlewareError } from '../../src/utils/errors.js'

describe('columnMappingController helpers', () => {
it('returns a 404 when column mapping request params are missing', async () => {
const res = {}
const next = vi.fn()

await handleUnavailableColumnMappingRequest({
locals: {
requestData: {
getParams: () => ({ dataset: 'tree' })
}
}
}, res, next)

expect(next).toHaveBeenCalledWith(expect.any(MiddlewareError))
expect(next.mock.calls[0][0].statusCode).toBe(404)

vi.clearAllMocks()

await handleUnavailableColumnMappingRequest({
locals: {
requestData: {
getParams: () => ({ organisationName: 'local-authority:TST' })
}
}
}, res, next)

expect(next).toHaveBeenCalledWith(expect.any(MiddlewareError))
expect(next.mock.calls[0][0].statusCode).toBe(404)

vi.clearAllMocks()

await handleUnavailableColumnMappingRequest({
locals: {}
}, res, next)

expect(next).toHaveBeenCalledWith(expect.any(MiddlewareError))
expect(next.mock.calls[0][0].statusCode).toBe(404)
})

it('builds rows from mapped, missing and unmapped columns', () => {
const rows = buildColumnMappingRows({
columnFieldLog: [
Expand Down
33 changes: 33 additions & 0 deletions test/unit/redisLoader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,37 @@ describe('getDatasetNameMap', () => {
vi.resetModules()
}
})

it('should return false for missing statutory dataset params without querying Datasette', async () => {
const mockDatasette = {
default: {
runQuery: vi.fn()
}
}

try {
vi.resetModules()
vi.doMock('../../config/index.js', () => ({
default: {
redis: false,
mainWebsiteUrl: config.mainWebsiteUrl
}
}))
vi.doMock('../../src/services/datasette.js', () => mockDatasette)

const { isStatutoryDataset } = await import('../../src/utils/redisLoader.js')

await expect(isStatutoryDataset(null)).resolves.toBe(false)
await expect(isStatutoryDataset()).resolves.toBe(false)
await expect(isStatutoryDataset({
organisation: 'local-authority:TST'
})).resolves.toBe(false)

expect(mockDatasette.default.runQuery).not.toHaveBeenCalled()
} finally {
vi.unmock('../../config/index.js')
vi.unmock('../../src/services/datasette.js')
vi.resetModules()
}
})
})
Loading