diff --git a/index.js b/index.js index c9ccbd9a..8b4ea0b7 100644 --- a/index.js +++ b/index.js @@ -11,8 +11,7 @@ import { setupErrorHandlers } from './src/serverSetup/errorHandlers.js' import { setupSession } from './src/serverSetup/session.js' import { setupNunjucks } from './src/serverSetup/nunjucks.js' import { setupSentry } from './src/serverSetup/sentry.js' - -import { dataSubjects } from './src/utils/utils.js' +import { getDatasetSlugNameMapping } from './src/utils/datasetteQueries/getDatasetSlugNameMapping.js' dotenv.config() @@ -20,7 +19,10 @@ const app = express() setupMiddlewares(app) setupSession(app) -setupNunjucks({ app, dataSubjects }) +setupNunjucks({ + app, + datasetNameMapping: await getDatasetSlugNameMapping() +}) setupRoutes(app) setupSentry(app) setupErrorHandlers(app) diff --git a/src/controllers/lpaDetailsController.js b/src/controllers/lpaDetailsController.js index 6d391848..92f11bec 100644 --- a/src/controllers/lpaDetailsController.js +++ b/src/controllers/lpaDetailsController.js @@ -1,5 +1,5 @@ import PageController from './pageController.js' -import { fetchLocalAuthorities } from '../services/fetchLocalAuthorities.js' +import { fetchLocalAuthorities } from '../utils/datasetteQueries/fetchLocalAuthorities.js' class LpaDetailsController extends PageController { async locals (req, res, next) { diff --git a/src/filters/filters.js b/src/filters/filters.js index 18c06ba9..c2c462d6 100644 --- a/src/filters/filters.js +++ b/src/filters/filters.js @@ -4,12 +4,11 @@ import validationMessageLookup from './validationMessageLookup.js' import toErrorList from './toErrorList.js' import prettifyColumnName from './prettifyColumnName.js' import getFullServiceName from './getFullServiceName.js' -import { makeDatasetSlugToReadableNameFilter, createDatasetMapping } from './makeDatasetSlugToReadableNameFilter.js' +import { makeDatasetSlugToReadableNameFilter } from './makeDatasetSlugToReadableNameFilter.js' const { govukMarkdown } = xGovFilters -const addFilters = (nunjucksEnv, { dataSubjects }) => { - const datasetNameMapping = createDatasetMapping(dataSubjects) +const addFilters = (nunjucksEnv, { datasetNameMapping }) => { const datasetSlugToReadableName = makeDatasetSlugToReadableNameFilter(datasetNameMapping) nunjucksEnv.addFilter('datasetSlugToReadableName', datasetSlugToReadableName) diff --git a/src/filters/getFullServiceName.js b/src/filters/getFullServiceName.js index b012e0af..353b592a 100644 --- a/src/filters/getFullServiceName.js +++ b/src/filters/getFullServiceName.js @@ -1,7 +1,10 @@ import config from '../../config/index.js' -export default (service) => { - const serviceName = config.serviceName - - return serviceName.replace('Provide', service) +const getFullServiceName = (service) => { + if (!service || typeof service !== 'string') { + throw new Error('Service name must be a non-empty string') + } + return config.serviceName.replace('Provide', service) } + +export default getFullServiceName diff --git a/src/filters/makeDatasetSlugToReadableNameFilter.js b/src/filters/makeDatasetSlugToReadableNameFilter.js index 06807cd3..9114555a 100644 --- a/src/filters/makeDatasetSlugToReadableNameFilter.js +++ b/src/filters/makeDatasetSlugToReadableNameFilter.js @@ -26,18 +26,3 @@ export const makeDatasetSlugToReadableNameFilter = (datasetNameMapping) => { return name } } - -/** - * - * @param {*} dataSubjects - * @returns {Map} - */ -export const createDatasetMapping = (dataSubjects) => { - const mapping = new Map() - for (const data of Object.values(dataSubjects)) { - for (const dataset of data.dataSets) { - mapping.set(dataset.value, dataset.text) - } - } - return mapping -} diff --git a/src/serverSetup/nunjucks.js b/src/serverSetup/nunjucks.js index e273dbc8..04370bd9 100644 --- a/src/serverSetup/nunjucks.js +++ b/src/serverSetup/nunjucks.js @@ -2,7 +2,7 @@ import nunjucks from 'nunjucks' import config from '../../config/index.js' import addFilters from '../filters/filters.js' -export function setupNunjucks ({ app, dataSubjects }) { +export function setupNunjucks ({ app, datasetNameMapping }) { if (app) { app.set('view engine', 'html') } @@ -33,7 +33,7 @@ export function setupNunjucks ({ app, dataSubjects }) { Object.keys(globalValues).forEach((key) => { nunjucksEnv.addGlobal(key, globalValues[key]) }) - addFilters(nunjucksEnv, { dataSubjects }) + addFilters(nunjucksEnv, { datasetNameMapping }) return nunjucks } diff --git a/src/services/datasette.js b/src/services/datasette.js index f7307a99..85018e24 100644 --- a/src/services/datasette.js +++ b/src/services/datasette.js @@ -5,15 +5,44 @@ const datasetteUrl = 'https://datasette.planning.data.gov.uk' const database = 'digital-land' export default { + /** + * Executes a SQL query on the Datasette instance and returns the results. + * + * @param {string} query - The SQL query to execute. + * @returns {Promise<{data: object, formattedData: object}>} - A promise that resolves to an object with the following properties: + * - `data`: The raw data returned by Datasette. + * - `formattedData`: The formatted data, with columns and rows parsed into a usable format. + * @throws {Error} If the query fails or there is an error communicating with Datasette. + */ runQuery: async (query) => { const encodedQuery = encodeURIComponent(query) const url = `${datasetteUrl}/${database}.json?sql=${encodedQuery}` try { const response = await axios.get(url) - return response.data + return { + ...response.data, + formattedData: formatData(response.data.columns, response.data.rows) + } } catch (error) { logger.warn(error) throw error } } } + +/** + * Formats an array of rows into an easier to access format, where each row is an object with column names as keys. + * + * @param {string[]} columns - An array of column names + * @param {any[][]} rows - A 2D array of row data, where each inner array represents a row + * @returns {object[]} - An array of objects, where each object represents a row with column names as keys + */ +export function formatData (columns, rows) { + // convert the rows into an easier to access format + return rows.map((row) => { + return row.reduce((acc, val, index) => { + acc[columns[index]] = val + return acc + }, {}) + }) +} diff --git a/src/services/performanceDbApi.js b/src/services/performanceDbApi.js index 17fe893a..6e2198e3 100644 --- a/src/services/performanceDbApi.js +++ b/src/services/performanceDbApi.js @@ -86,19 +86,10 @@ ORDER BY const result = await datasette.runQuery(query) - // convert the rows into an easier to access format - const columns = result.columns - const rows = result.rows.map((row) => { - return row.reduce((acc, val, index) => { - acc[columns[index]] = val - return acc - }, {}) - }) - - const datasets = rows.reduce((accumulator, row) => { + const datasets = result.formattedData.reduce((accumulator, row) => { let error - if (row.http_status !== '200' || row.exception !== '') { - error = row.exception !== '' ? row.exception : `endpoint returned with a status of ${row.http_status}` + if (row.http_status !== '200' || row.exception) { + error = row.exception ? row.exception : `endpoint returned with a status of ${row.http_status}` } let issue @@ -115,7 +106,7 @@ ORDER BY }, {}) return { - name: result.rows[0][1], + name: result.formattedData[0].name, datasets } } diff --git a/src/services/fetchLocalAuthorities.js b/src/utils/datasetteQueries/fetchLocalAuthorities.js similarity index 78% rename from src/services/fetchLocalAuthorities.js rename to src/utils/datasetteQueries/fetchLocalAuthorities.js index 758b692a..865ad3b9 100644 --- a/src/services/fetchLocalAuthorities.js +++ b/src/utils/datasetteQueries/fetchLocalAuthorities.js @@ -1,5 +1,5 @@ -import axios from 'axios' -import logger from '../../src/utils/logger.js' +import datasette from '../../services/datasette.js' +import logger from '../logger.js' /** * Fetches a list of local authority names from a specified dataset. @@ -24,15 +24,14 @@ export const fetchLocalAuthorities = async () => { order by provision.organisation` - const url = `https://datasette.planning.data.gov.uk/digital-land.json?sql=${encodeURIComponent(sql)}` try { - const response = await axios.get(url) - const names = response.data.rows.map(row => { - if (row[1] === null) { + const response = await datasette.runQuery(sql) + const names = response.formattedData.map(row => { + if (row.name == null) { logger.debug('Null value found in response:', row) return null } else { - return row[1] + return row.name } }).filter(name => name !== null) // Filter out null values return names diff --git a/src/utils/datasetteQueries/getDatasetSlugNameMapping.js b/src/utils/datasetteQueries/getDatasetSlugNameMapping.js new file mode 100644 index 00000000..43d85225 --- /dev/null +++ b/src/utils/datasetteQueries/getDatasetSlugNameMapping.js @@ -0,0 +1,11 @@ +import datasette from '../../services/datasette.js' + +export const getDatasetSlugNameMapping = async () => { + const datasetSlugNameTable = await datasette.runQuery('select dataset, name from dataset') + + const datasetMapping = new Map() + datasetSlugNameTable.rows.forEach(([slug, name]) => { + datasetMapping.set(slug, name) + }) + return datasetMapping +} diff --git a/test/unit/check-answers.test.js b/test/unit/check-answers.test.js index 69f505dc..8755f407 100644 --- a/test/unit/check-answers.test.js +++ b/test/unit/check-answers.test.js @@ -5,7 +5,6 @@ import { setupNunjucks } from '../../src/serverSetup/nunjucks.js' import { runGenericPageTests } from './generic-page.js' import config from '../../config/index.js' import { stripWhitespace } from '../utils/stripWhiteSpace.js' -import { mockDataSubjects } from './data.js' describe('check-answers View', async () => { const params = { @@ -19,7 +18,7 @@ describe('check-answers View', async () => { hasLicence: 'true' } } - const nunjucks = setupNunjucks({ dataSubjects: mockDataSubjects }) + const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) const html = stripWhitespace(nunjucks.render('check-answers.html', params)) runGenericPageTests(html, { @@ -43,7 +42,7 @@ describe('check-answers View', async () => { }) it('should render the dataset entered', () => { - const datasetRegex = new RegExp('
.*Dataset.*A Mock dataset.*Change.*
', 'g') + const datasetRegex = new RegExp('
.*Dataset.*mockDataset.*Change.*
', 'g') expect(html).toMatch(datasetRegex) }) diff --git a/test/unit/check/confirmationPage.test.js b/test/unit/check/confirmationPage.test.js index 8a668ffa..aaee1a23 100644 --- a/test/unit/check/confirmationPage.test.js +++ b/test/unit/check/confirmationPage.test.js @@ -5,9 +5,8 @@ import { setupNunjucks } from '../../../src/serverSetup/nunjucks.js' import { runGenericPageTests } from '../generic-page.js' import config from '../../../config/index.js' import { stripWhitespace } from '../../utils/stripWhiteSpace.js' -import { mockDataSubjects } from '../data.js' -const nunjucks = setupNunjucks({ dataSubjects: mockDataSubjects }) +const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) describe('Check confirmation View', () => { const params = { @@ -18,12 +17,12 @@ describe('Check confirmation View', () => { const html = stripWhitespace(nunjucks.render('submit/confirmation.html', params)) runGenericPageTests(html, { - pageTitle: 'A Mock dataset submitted - Submit planning and housing data for England', + pageTitle: 'mockDataset submitted - Submit planning and housing data for England', serviceName: config.serviceName }) it('should render the gov uk panel', () => { - const regex = new RegExp('

', 'g') + const regex = new RegExp('

', 'g') expect(html).toMatch(regex) }) }) diff --git a/test/unit/choose-datasetPage.test.js b/test/unit/choose-datasetPage.test.js index 13e07b3c..78f77a4d 100644 --- a/test/unit/choose-datasetPage.test.js +++ b/test/unit/choose-datasetPage.test.js @@ -4,7 +4,7 @@ import { runGenericPageTests } from './generic-page.js' import config from '../../config/index.js' import { testValidationErrorMessage } from './validation-tests.js' -const nunjucks = setupNunjucks({ dataSubjects: {} }) +const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) describe('choose dataset View', () => { const params = { diff --git a/test/unit/dataset-details.test.js b/test/unit/dataset-details.test.js index ff5de20d..91af3709 100644 --- a/test/unit/dataset-details.test.js +++ b/test/unit/dataset-details.test.js @@ -8,7 +8,7 @@ import { stripWhitespace } from '../utils/stripWhiteSpace.js' import { testValidationErrorMessage } from './validation-tests.js' import { mockDataSubjects } from './data.js' -const nunjucks = setupNunjucks({ dataSubjects: mockDataSubjects }) +const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) function errorTestFn ({ params, @@ -41,7 +41,7 @@ describe('dataset details View', () => { errors: {} } const html = stripWhitespace(nunjucks.render('dataset-details.html', params)) - const datasetName = mockDataSubjects.mockDataset.dataSets[0].text + const datasetName = mockDataSubjects.mockDataset.dataSets[0].value runGenericPageTests(html, { pageTitle: `Enter ${datasetName.toLowerCase()} details - Submit planning and housing data for England`, serviceName: config.serviceName diff --git a/test/unit/datasette.test.js b/test/unit/datasette.test.js new file mode 100644 index 00000000..f7cbb956 --- /dev/null +++ b/test/unit/datasette.test.js @@ -0,0 +1,55 @@ +import datasette, { formatData } from '../../src/services/datasette.js' +import axios from 'axios' +import { vi, describe, beforeEach, afterEach, it, expect } from 'vitest' + +describe('datasette', () => { + beforeEach(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: { + columns: ['column1', 'column2'], + rows: [ + ['value1', 'value2'], + ['value3', 'value4'] + ] + } + }) + }) + + afterEach(() => { + vi.mocked(axios.get).mockReset() + }) + + it('runs a SQL query and returns the results', async () => { + const query = 'SELECT * FROM table_name' + const result = await datasette.runQuery(query) + + expect(result).toEqual({ + columns: ['column1', 'column2'], + formattedData: [ + { column1: 'value1', column2: 'value2' }, + { column1: 'value3', column2: 'value4' } + ], + rows: [ + ['value1', 'value2'], + ['value3', 'value4'] + ] + }) + }) + + it('throws an error if the query fails', async () => { + vi.spyOn(axios, 'get').mockRejectedValue(new Error('Query failed')) + + await expect(datasette.runQuery('SELECT * FROM table_name')).rejects.toThrowError('Query failed') + }) + + it('formats data correctly', () => { + const columns = ['column1', 'column2'] + const rows = [['value1', 'value2'], ['value3', 'value4']] + const formattedData = formatData(columns, rows) + + expect(formattedData).toEqual([ + { column1: 'value1', column2: 'value2' }, + { column1: 'value3', column2: 'value4' } + ]) + }) +}) diff --git a/test/unit/endpointSubmissionForm/confirmationPage.test.js b/test/unit/endpointSubmissionForm/confirmationPage.test.js index cc624949..897d4968 100644 --- a/test/unit/endpointSubmissionForm/confirmationPage.test.js +++ b/test/unit/endpointSubmissionForm/confirmationPage.test.js @@ -5,9 +5,8 @@ import { setupNunjucks } from '../../../src/serverSetup/nunjucks.js' import { runGenericPageTests } from '../generic-page.js' import config from '../../../config/index.js' import { stripWhitespace } from '../../utils/stripWhiteSpace.js' -import { mockDataSubjects } from '../data.js' -const nunjucks = setupNunjucks({ dataSubjects: mockDataSubjects }) +const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) describe('Submit confirmation View', () => { const params = { @@ -18,12 +17,12 @@ describe('Submit confirmation View', () => { const html = stripWhitespace(nunjucks.render('submit/confirmation.html', params)) runGenericPageTests(html, { - pageTitle: 'A Mock dataset submitted - Submit planning and housing data for England', + pageTitle: 'mockDataset submitted - Submit planning and housing data for England', serviceName: config.serviceName }) it('should render the gov uk panel', () => { - const regex = new RegExp('

', 'g') + const regex = new RegExp('

', 'g') expect(html).toMatch(regex) }) }) diff --git a/test/unit/fetchLocalAuthorities.test.js b/test/unit/fetchLocalAuthorities.test.js index 23380347..aa0a5846 100644 --- a/test/unit/fetchLocalAuthorities.test.js +++ b/test/unit/fetchLocalAuthorities.test.js @@ -1,39 +1,39 @@ -import axios from 'axios' import { vi, it, describe, expect } from 'vitest' -import { fetchLocalAuthorities } from '../../src/services/fetchLocalAuthorities' +import datasette from '../../src/services/datasette.js' +import { fetchLocalAuthorities } from '../../src/utils/datasetteQueries/fetchLocalAuthorities' -// Mock axios.get to return a fake response -vi.mock('axios') -axios.get.mockResolvedValue({ - data: { - rows: [ - [1, 'Local Authority 1'], - [2, 'Local Authority 2'], - [3, 'Local Authority 3'] - ] +// Mock datasette.runQuery to return a fake response +vi.mock('../../src/services/datasette.js', () => ({ + default: { + runQuery: vi.fn() } -}) +})) describe('fetchLocalAuthorities', () => { it('should fetch local authority names', async () => { + datasette.runQuery.mockResolvedValue({ + formattedData: [ + { name: 'Local Authority 1' }, + { name: 'Local Authority 2' }, + { name: 'Local Authority 3' } + ] + }) const result = await fetchLocalAuthorities() expect(result).toEqual(['Local Authority 1', 'Local Authority 2', 'Local Authority 3']) }) it('should throw an error if the HTTP request fails', async () => { - axios.get.mockRejectedValue(new Error('Failed to fetch data')) + datasette.runQuery.mockRejectedValue(new Error('Failed to fetch data')) await expect(fetchLocalAuthorities()).rejects.toThrow('Failed to fetch data') }) it('should throw an error if data processing encounters an issue', async () => { - axios.get.mockResolvedValue({ - data: { - rows: [ - [1, 'Local Authority 1'], - [2, null], // Simulate null value in the response - [3, 'Local Authority 3'] - ] - } + datasette.runQuery.mockResolvedValue({ + formattedData: [ + { name: 'Local Authority 1' }, + { name: null }, // Simulate null value in the response + { name: 'Local Authority 3' } + ] }) const result = await fetchLocalAuthorities() expect(result).toEqual(['Local Authority 1', 'Local Authority 3']) diff --git a/test/unit/getDatasetSlugNameMapping.test.js b/test/unit/getDatasetSlugNameMapping.test.js new file mode 100644 index 00000000..6ef2459d --- /dev/null +++ b/test/unit/getDatasetSlugNameMapping.test.js @@ -0,0 +1,34 @@ +import { vi, it, describe, expect } from 'vitest' +import datasette from '../../src/services/datasette.js' +import { getDatasetSlugNameMapping } from '../../src/utils/datasetteQueries/getDatasetSlugNameMapping.js' + +// Mock datasette.runQuery to return a fake response +vi.mock('../../src/services/datasette.js', () => ({ + default: { + runQuery: vi.fn() + } +})) + +describe('getDatasetSlugNameMapping', () => { + it('returns a Map with dataset slugs as keys and names as values', async () => { + datasette.runQuery.mockResolvedValue({ + rows: [ + ['dataset-slug-1', 'Dataset Name 1'], + ['dataset-slug-2', 'Dataset Name 2'] + ] + }) + + const result = await getDatasetSlugNameMapping() + + expect(result).toBeInstanceOf(Map) + expect(result.size).toBe(2) + expect(result.get('dataset-slug-1')).toBe('Dataset Name 1') + expect(result.get('dataset-slug-2')).toBe('Dataset Name 2') + }) + + it('throws an error if datasette.runQuery fails', async () => { + datasette.runQuery.mockRejectedValue(new Error('Error running query')) + + await expect(getDatasetSlugNameMapping()).rejects.toThrowError('Error running query') + }) +}) diff --git a/test/unit/getFullServiceName.test.js b/test/unit/getFullServiceName.test.js new file mode 100644 index 00000000..3c47bbb5 --- /dev/null +++ b/test/unit/getFullServiceName.test.js @@ -0,0 +1,28 @@ +// getFullServiceName.test.js +import { vi, it, describe, expect } from 'vitest' + +vi.mock('../../config/index.js', () => ({ default: { serviceName: 'Provide Example' } })) + +describe('getFullServiceName', async () => { + const getFullServiceName = (await vi.importActual('../../src/filters/getFullServiceName.js')).default + + it('returns the full service name by replacing "Provide" with the service name', () => { + const serviceName = 'MyService' + const result = getFullServiceName(serviceName) + + expect(result).toBe('MyService Example') + }) + + it('throws an error if service name is not a string', () => { + expect(() => getFullServiceName(123)).toThrowError('Service name must be a non-empty string') + }) + + it('throws an error if service name is empty', () => { + expect(() => getFullServiceName('')).toThrowError('Service name must be a non-empty string') + }) + + it('throws an error if service name is null or undefined', () => { + expect(() => getFullServiceName(null)).toThrowError('Service name must be a non-empty string') + expect(() => getFullServiceName(undefined)).toThrowError('Service name must be a non-empty string') + }) +}) diff --git a/test/unit/lpa-detailsPage.test.js b/test/unit/lpa-detailsPage.test.js index 5055add1..afc8389b 100644 --- a/test/unit/lpa-detailsPage.test.js +++ b/test/unit/lpa-detailsPage.test.js @@ -3,9 +3,8 @@ import { setupNunjucks } from '../../src/serverSetup/nunjucks.js' import { runGenericPageTests } from './generic-page.js' import config from '../../config/index.js' import { testValidationErrorMessage } from './validation-tests.js' -import { mockDataSubjects } from './data.js' -const nunjucks = setupNunjucks({ dataSubjects: mockDataSubjects }) +const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) describe('Lpa-details View', () => { const params = { diff --git a/test/unit/lpaDetailsController.test.js b/test/unit/lpaDetailsController.test.js index 2d98156b..b69459ac 100644 --- a/test/unit/lpaDetailsController.test.js +++ b/test/unit/lpaDetailsController.test.js @@ -4,14 +4,14 @@ import PageController from '../../src/controllers/pageController.js' import { vi, it, describe, expect, beforeEach, afterEach } from 'vitest' -vi.mock('../../src/services/fetchLocalAuthorities.js') +vi.mock('../../src/utils/datasetteQueries/fetchLocalAuthorities.js') describe('lpaDetailsController', async () => { let fetchLocalAuthorities let controller beforeEach(async () => { - fetchLocalAuthorities = await import('../../src/services/fetchLocalAuthorities') + fetchLocalAuthorities = await import('../../src/utils/datasetteQueries/fetchLocalAuthorities') const LpaDetailsController = await import('../../src/controllers/lpaDetailsController.js') controller = new LpaDetailsController.default({ route: '/lpa-details' diff --git a/test/unit/lpaOverviewPage.test.js b/test/unit/lpaOverviewPage.test.js index 1253e98d..624f0818 100644 --- a/test/unit/lpaOverviewPage.test.js +++ b/test/unit/lpaOverviewPage.test.js @@ -4,8 +4,7 @@ import nunjucks from 'nunjucks' import addFilters from '../../src/filters/filters' import { runGenericPageTests } from './generic-page.js' import jsdom from 'jsdom' -import { dataSubjects } from '../../src/utils/utils.js' -import { makeDatasetSlugToReadableNameFilter, createDatasetMapping } from '../../src/filters/makeDatasetSlugToReadableNameFilter.js' +import { makeDatasetSlugToReadableNameFilter } from '../../src/filters/makeDatasetSlugToReadableNameFilter.js' const nunjucksEnv = nunjucks.configure([ 'src/views', @@ -19,7 +18,11 @@ const nunjucksEnv = nunjucks.configure([ watch: true }) -addFilters(nunjucksEnv, { dataSubjects }) +const datasetNameMapping = new Map([ + +]) + +addFilters(nunjucksEnv, { datasetNameMapping }) describe('LPA Overview Page', () => { const params = { @@ -108,8 +111,7 @@ describe('LPA Overview Page', () => { it('The correct number of dataset cards are rendered with the correct titles', () => { expect(datasetCards.length).toEqual(params.datasets.length) - const datasetMapping = createDatasetMapping(dataSubjects) - const datasetSlugToReadableName = makeDatasetSlugToReadableNameFilter(datasetMapping) + const datasetSlugToReadableName = makeDatasetSlugToReadableNameFilter(datasetNameMapping) params.datasets.forEach((dataset, i) => { expect(datasetCards[i].querySelector('.govuk-heading-m').textContent).toContain(datasetSlugToReadableName(dataset.slug)) diff --git a/test/unit/makeDatasetSlugToReadableNameFilter.test.js b/test/unit/makeDatasetSlugToReadableNameFilter.test.js new file mode 100644 index 00000000..12d26b73 --- /dev/null +++ b/test/unit/makeDatasetSlugToReadableNameFilter.test.js @@ -0,0 +1,31 @@ +// makeDatasetSlugToReadableNameFilter.test.js +import { vi, it, describe, expect } from 'vitest' +import logger from '../../src/utils/logger.js' +import { makeDatasetSlugToReadableNameFilter } from '../../src/filters/makeDatasetSlugToReadableNameFilter' + +describe('makeDatasetSlugToReadableNameFilter', () => { + const datasetNameMapping = new Map([ + ['dataset-slug-1', 'Dataset 1'], + ['dataset-slug-2', 'Dataset 2'] + ]) + + const filter = makeDatasetSlugToReadableNameFilter(datasetNameMapping) + + it('returns a function that takes a dataset slug and returns its corresponding readable name', () => { + expect(filter('dataset-slug-1')).toBe('Dataset 1') + expect(filter('dataset-slug-2')).toBe('Dataset 2') + }) + + it('returns the original slug if it is not found in the dataset name mapping', () => { + expect(filter('unknown-slug')).toBe('unknown-slug') + }) + + it('logs an warn if the provided slug is not found in the dataset name mapping', () => { + const loggerWarningSpy = vi.spyOn(logger, 'warn') + + filter('unknown-slug') + + expect(loggerWarningSpy).toHaveBeenCalledTimes(1) + expect(loggerWarningSpy).toHaveBeenCalledWith('can\'t find a name for unknown-slug') + }) +}) diff --git a/test/unit/performanceDbApi.test.js b/test/unit/performanceDbApi.test.js new file mode 100644 index 00000000..79a8dc56 --- /dev/null +++ b/test/unit/performanceDbApi.test.js @@ -0,0 +1,62 @@ +import { vi, it, describe, expect } from 'vitest' +import datasette from '../../src/services/datasette' +import performanceDbApi from '../../src/services/performanceDbApi' + +describe('performanceDbApi', () => { + describe('getLpaOverview', () => { + it('calls datasette.runQuery with the correct query', async () => { + const lpa = 'some-lpa-id' + const mockResponse = { + formattedData: [ + { + organisation: 'some-organisation', + name: 'Some Organisation', + dataset: 'dataset-slug-1', + endpoint: 'https://example.com/endpoint-1', + exception: null, + http_status: '404' + }, + { + organisation: 'some-organisation', + name: 'Some Organisation', + dataset: 'dataset-slug-2', + endpoint: 'https://example.com/endpoint-2', + exception: 'resource not found', + http_status: '404' + }, + { + organisation: 'some-organisation', + name: 'Some Organisation', + dataset: 'dataset-slug-3', + endpoint: 'https://example.com/endpoint-3', + http_status: '200', + issue_count: 4 + } + ] + } + + vi.spyOn(datasette, 'runQuery').mockResolvedValue(mockResponse) + + const result = await performanceDbApi.getLpaOverview(lpa) + + expect(datasette.runQuery).toHaveBeenCalledTimes(1) + expect(datasette.runQuery).toHaveBeenCalledWith(expect.stringContaining(lpa)) + expect(result).toEqual({ + name: 'Some Organisation', + datasets: { + 'dataset-slug-1': { endpoint: 'https://example.com/endpoint-1', error: 'endpoint returned with a status of 404', issue: undefined }, + 'dataset-slug-2': { endpoint: 'https://example.com/endpoint-2', error: 'resource not found', issue: undefined }, + 'dataset-slug-3': { endpoint: 'https://example.com/endpoint-3', error: undefined, issue: 'There are 4 issues in this dataset' } + } + }) + }) + + it('returns an error if the query fails', async () => { + const lpa = 'some-lpa-id' + const error = new Error('query failed') + vi.spyOn(datasette, 'runQuery').mockRejectedValue(error) + + await expect(performanceDbApi.getLpaOverview(lpa)).rejects.toThrowError('query failed') + }) + }) +}) diff --git a/test/unit/startPage.test.js b/test/unit/startPage.test.js index be17d9f8..3d147de5 100644 --- a/test/unit/startPage.test.js +++ b/test/unit/startPage.test.js @@ -4,9 +4,8 @@ import { describe } from 'vitest' import { setupNunjucks } from '../../src/serverSetup/nunjucks.js' import { runGenericPageTests } from './generic-page.js' import config from '../../config/index.js' -import { mockDataSubjects } from './data.js' -const nunjucks = setupNunjucks({ dataSubjects: mockDataSubjects }) +const nunjucks = setupNunjucks({ datasetNameMapping: new Map() }) describe('Start View', () => { const params = {}