From 0e649489b236981689c7c4cf8af48a530b2eae48 Mon Sep 17 00:00:00 2001 From: Alan Cruikshanks Date: Tue, 23 Apr 2024 14:58:34 +0100 Subject: [PATCH] Migrate view bill runs page from legacy UI (#925) https://eaflood.atlassian.net/browse/WATER-4445 > Part of a series of changes to migrate the legacy view bill runs page into this project This is the final step in migrating the legacy view bill runs page to this project. We dealt with the support services we need in previous changes. Now we need to add - the route - the controller handler - the orchestration service - the view template We also update the menu to point to it and not the legacy `/billing/batch/list`. Worth noting (because this is the first) we use the convention of naming the handler and subsequent files `index` to reflect they deal with displaying an index of bill runs, not a single one. --- app/controllers/bill-runs-setup.controller.js | 2 +- app/controllers/bill-runs.controller.js | 15 +- .../bill-runs/index-bill-runs.presenter.js | 66 +++++++++ app/routes/bill-runs.routes.js | 13 ++ .../bill-runs/index-bill-runs.service.js | 64 ++++++++ app/views/bill-runs/empty.njk | 2 +- app/views/bill-runs/errored.njk | 2 +- app/views/bill-runs/index.njk | 138 ++++++++++++++++++ app/views/bill-runs/review.njk | 2 +- app/views/bill-runs/setup/type.njk | 2 +- app/views/bill-runs/view.njk | 2 +- app/views/includes/nav-bar.njk | 2 +- app/views/macros/bill-run-status-tag.njk | 24 ++- .../bill-runs-setup.controller.test.js | 2 +- test/controllers/bill-runs.controller.test.js | 58 +++++++- .../index.bill-runs.presenter.test.js | 118 +++++++++++++++ .../bill-runs/index-bill-runs.service.test.js | 136 +++++++++++++++++ 17 files changed, 630 insertions(+), 18 deletions(-) create mode 100644 app/presenters/bill-runs/index-bill-runs.presenter.js create mode 100644 app/services/bill-runs/index-bill-runs.service.js create mode 100644 app/views/bill-runs/index.njk create mode 100644 test/presenters/bill-runs/index.bill-runs.presenter.test.js create mode 100644 test/services/bill-runs/index-bill-runs.service.test.js diff --git a/app/controllers/bill-runs-setup.controller.js b/app/controllers/bill-runs-setup.controller.js index 58dc2ddd1e..0624ebe5e8 100644 --- a/app/controllers/bill-runs-setup.controller.js +++ b/app/controllers/bill-runs-setup.controller.js @@ -38,7 +38,7 @@ async function create (request, h) { try { await CreateService.go(request.auth.credentials.user, results) - return h.redirect('/billing/batch/list') + return h.redirect('/system/bill-runs') } catch (error) { return Boom.badImplementation(error.message) } diff --git a/app/controllers/bill-runs.controller.js b/app/controllers/bill-runs.controller.js index 4100ece52d..1ea584cce2 100644 --- a/app/controllers/bill-runs.controller.js +++ b/app/controllers/bill-runs.controller.js @@ -9,6 +9,7 @@ const Boom = require('@hapi/boom') const CancelBillRunService = require('../services/bill-runs/cancel-bill-run.service.js') const CreateBillRunValidator = require('../validators/create-bill-run.validator.js') +const IndexBillRunsService = require('../services/bill-runs/index-bill-runs.service.js') const MatchDetailsService = require('../services/bill-runs/two-part-tariff/match-details.service.js') const ReviewBillRunService = require('../services/bill-runs/two-part-tariff/review-bill-run.service.js') const ReviewLicenceService = require('../services/bill-runs/two-part-tariff/review-licence.service.js') @@ -47,6 +48,17 @@ async function create (request, h) { } } +async function index (request, h) { + const { page } = request.query + + const pageData = await IndexBillRunsService.go(page) + + return h.view('bill-runs/index.njk', { + activeNavBar: 'bill-runs', + ...pageData + }) +} + async function matchDetails (request, h) { const { id: billRunId, licenceId, reviewChargeElementId } = request.params @@ -102,7 +114,7 @@ async function submitCancel (request, h) { // `cancel'. await SubmitCancelBillRunService.go(id) - return h.redirect('/billing/batch/list') + return h.redirect('/system/bill-runs') } catch (error) { return Boom.badImplementation(error.message) } @@ -137,6 +149,7 @@ async function view (request, h) { module.exports = { cancel, create, + index, matchDetails, review, reviewLicence, diff --git a/app/presenters/bill-runs/index-bill-runs.presenter.js b/app/presenters/bill-runs/index-bill-runs.presenter.js new file mode 100644 index 0000000000..c42b5065ee --- /dev/null +++ b/app/presenters/bill-runs/index-bill-runs.presenter.js @@ -0,0 +1,66 @@ +'use strict' + +/** + * Formats the summary data for each bill run for use in the /bill-runs page + * @module IndexBillRunsPresenter + */ + +const { + capitalize, + formatBillRunType, + formatLongDate, + formatMoney +} = require('../base.presenter.js') + +/** + * Formats the summary data for each bill run for use in the /bill-runs page + * + * @param {module:BillRunModel[]} billRuns - The bill runs containing the data to be summarised for the view + * + * @returns {Object[]} Each bill run summary formatted for use in the `index.njk` template for `/bill-runs` + */ +function go (billRuns) { + return billRuns.map((billRun) => { + const { + batchType, + billRunNumber, + createdAt, + id, + netTotal, + numberOfBills, + region, + scheme, + summer, + status + } = billRun + + return { + id, + createdAt: formatLongDate(createdAt), + link: _link(id, status), + number: billRunNumber, + numberOfBills, + region: capitalize(region), + scheme, + status, + total: formatMoney(netTotal, true), + type: formatBillRunType(batchType, scheme, summer) + } + }) +} + +function _link (billRunId, status) { + if (status === 'cancel') { + return null + } + + if (status === 'review') { + return `/system/bill-runs/${billRunId}/review` + } + + return `/system/bill-runs/${billRunId}` +} + +module.exports = { + go +} diff --git a/app/routes/bill-runs.routes.js b/app/routes/bill-runs.routes.js index ed53587144..5528429062 100644 --- a/app/routes/bill-runs.routes.js +++ b/app/routes/bill-runs.routes.js @@ -3,6 +3,19 @@ const BillRunsController = require('../controllers/bill-runs.controller.js') const routes = [ + { + method: 'GET', + path: '/bill-runs', + handler: BillRunsController.index, + options: { + auth: { + access: { + scope: ['billing'] + } + }, + description: 'List all bill runs' + } + }, { method: 'POST', path: '/bill-runs', diff --git a/app/services/bill-runs/index-bill-runs.service.js b/app/services/bill-runs/index-bill-runs.service.js new file mode 100644 index 0000000000..ee4a138ad6 --- /dev/null +++ b/app/services/bill-runs/index-bill-runs.service.js @@ -0,0 +1,64 @@ +'use strict' + +/** + * Orchestrates fetching and presenting the data needed for the /bill-runs page + * @module IndexBillRunsService + */ + +const CheckBusyBillRunsService = require('./check-busy-bill-runs.service.js') +const FetchBillRunsService = require('./fetch-bill-runs.service.js') +const IndexBillRunsPresenter = require('../../presenters/bill-runs/index-bill-runs.presenter.js') +const PaginatorPresenter = require('../../presenters/paginator.presenter.js') + +/** + * Orchestrates fetching and presenting the data needed for the /bill-runs page + * + * @param {string} page - the page number of bill runs to be viewed + * + * @returns {Promise} an object representing the `pageData` needed by the index bill run template. It contains + * summary details for each bill run for the page selected, the template's pagination control, the title and the + * status of any busy bill runs + */ +async function go (page) { + const selectedPageNumber = _selectedPageNumber(page) + + // We expect the FetchBillRunsService to take longer to complete than CheckBusyBillRunsService. But running them + // together means we are only waiting as long as it takes FetchBillRunsService to complete rather than their combined + // time + const [fetchedBillRunResult, busyResult] = await Promise.all([ + FetchBillRunsService.go(selectedPageNumber), + CheckBusyBillRunsService.go() + ]) + + const billRuns = IndexBillRunsPresenter.go(fetchedBillRunResult.results) + const pagination = PaginatorPresenter.go(fetchedBillRunResult.total, selectedPageNumber, '/system/bill-runs') + + const pageTitle = _pageTitle(pagination.numberOfPages, selectedPageNumber) + + return { + billRuns, + busy: busyResult, + pageTitle, + pagination + } +} + +function _pageTitle (numberOfPages, selectedPageNumber) { + if (numberOfPages === 1) { + return 'Bill runs' + } + + return `Bill runs (page ${selectedPageNumber} of ${numberOfPages})` +} + +function _selectedPageNumber (page) { + if (!page) { + return 1 + } + + return Number(page) +} + +module.exports = { + go +} diff --git a/app/views/bill-runs/empty.njk b/app/views/bill-runs/empty.njk index 75ab34cf99..93062b4fa8 100644 --- a/app/views/bill-runs/empty.njk +++ b/app/views/bill-runs/empty.njk @@ -11,7 +11,7 @@ {{ govukBackLink({ text: 'Go back to bill runs', - href: '/billing/batch/list' + href: '/system/bill-runs' }) }} {% endblock %} diff --git a/app/views/bill-runs/errored.njk b/app/views/bill-runs/errored.njk index bed31cdee1..59be4790f7 100644 --- a/app/views/bill-runs/errored.njk +++ b/app/views/bill-runs/errored.njk @@ -11,7 +11,7 @@ {{ govukBackLink({ text: 'Go back to bill runs', - href: '/billing/batch/list' + href: '/system/bill-runs' }) }} {% endblock %} diff --git a/app/views/bill-runs/index.njk b/app/views/bill-runs/index.njk new file mode 100644 index 0000000000..5dbd58b501 --- /dev/null +++ b/app/views/bill-runs/index.njk @@ -0,0 +1,138 @@ +{% extends 'layout.njk' %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/notification-banner/macro.njk" import govukNotificationBanner %} +{% from "govuk/components/pagination/macro.njk" import govukPagination %} +{% from "govuk/components/table/macro.njk" import govukTable %} + +{% from "macros/bill-run-status-tag.njk" import statusTag %} + +{% block content %} +
+
+ {# Bill runs busy banner #} + {% if busy == 'both' %} + {{ govukNotificationBanner({ + html: '

Bill runs are currently busy building and cancelling.

+

Please wait for these bill runs to finish before creating another one.

' + }) }} + {% elif busy == 'building' %} + {{ govukNotificationBanner({ + html: '

A bill run is currently building.

+

Please wait for this bill run to finish building before creating another one.

' + }) }} + {% elif busy == 'cancelling' %} + {{ govukNotificationBanner({ + html: '

A bill run is currently cancelling.

+

Please wait for this bill run to finish cancelling before creating another one.

' + }) }} + {% endif %} + +

Bill runs

+ +

Create a supplementary, annual or two-part tariff bill run.

+ + {{ govukButton({ + text: "Create a bill run", + href: "/system/bill-runs/setup" + }) }} + +
+ +
+
+ +
+
+ {# Results #} + {% if billRuns|length == 0 %} +

No bill runs found.

+ {% else %} +

View a bill run

+ +

Select date to see the details of a bill run.

+ + {% set tableRows = [] %} + {% for billRun in billRuns %} + {# Set an easier to use index #} + {% set rowIndex = loop.index0 %} + + + {# Link to view the bill run #} + {% set viewLink %} + {# If the link is null it is because the bill run is cancelling so we do not want to display a link #} + {% if billRun.link %} + {{ billRun.createdAt }} View bill run {{ billRun.number }} + {% else %} + {{ billRun.createdAt }} + {% endif %} + + {% if billRun.scheme == 'alcs' %} +
Old charge scheme
+ {% endif %} + {% endset %} + + {% set billRunStatusTag %} + {{ statusTag(billRun.status, true) }} + {% endset %} + + {% set tableRow = [ + { + html: viewLink, + attributes: { 'data-test': 'date-created-' + rowIndex } + }, + { + text: billRun.region, + attributes: { 'data-test': 'region-' + rowIndex } + }, + { + text: billRun.type, + attributes: { 'data-test': 'bill-run-type-' + rowIndex } + }, + { + text: billRun.number, + attributes: { 'data-test': 'bill-run-number-' + rowIndex }, + format: 'numeric' + }, + { + text: billRun.numberOfBills, + attributes: { 'data-test': 'number-of-bills-' + rowIndex }, + format: 'numeric' + }, + { + text: billRun.total, + attributes: { 'data-test': 'bill-run-total-' + rowIndex }, + format: 'numeric' + }, + { + html: billRunStatusTag, + attributes: { 'data-test': 'bill-run-status-' + rowIndex }, + format: 'numeric' + } + ] %} + + {# Push our row into the table rows array #} + {% set tableRows = (tableRows.push(tableRow), tableRows) %} + {% endfor %} + + {{ govukTable({ + firstCellIsHeader: false, + attributes: { 'data-test': 'bill-runs'}, + head: [ + { text: 'Date' }, + { text: 'Region' }, + { text: 'Run type' }, + { text: 'Number', format: 'numeric' }, + { text: 'Bills', format: 'numeric' }, + { text: 'Values', format: 'numeric' }, + { text: 'Status', format: 'numeric' } + ], + rows: tableRows + }) }} + + {% if pagination.numberOfPages > 1 %} + {{ govukPagination(pagination.component) }} + {% endif %} + {% endif %} +
+
+{% endblock %} diff --git a/app/views/bill-runs/review.njk b/app/views/bill-runs/review.njk index 3ec5ff6389..48c8fa1880 100644 --- a/app/views/bill-runs/review.njk +++ b/app/views/bill-runs/review.njk @@ -16,7 +16,7 @@ {# Back link #} {{ govukBackLink({ text: 'Go back to bill runs', - href: '/billing/batch/list' + href: '/system/bill-runs' }) }} {% endblock %} diff --git a/app/views/bill-runs/setup/type.njk b/app/views/bill-runs/setup/type.njk index ecb181a5b4..8c75e4c129 100644 --- a/app/views/bill-runs/setup/type.njk +++ b/app/views/bill-runs/setup/type.njk @@ -9,7 +9,7 @@ {{ govukBackLink({ text: 'Go back to bill runs', - href: '/billing/batch/list' + href: '/system/bill-runs' }) }} {% endblock %} diff --git a/app/views/bill-runs/view.njk b/app/views/bill-runs/view.njk index 326c78d304..eed8cd83b2 100644 --- a/app/views/bill-runs/view.njk +++ b/app/views/bill-runs/view.njk @@ -12,7 +12,7 @@ {{ govukBackLink({ text: 'Go back to bill runs', - href: '/billing/batch/list' + href: '/system/bill-runs' }) }} {% endblock %} diff --git a/app/views/includes/nav-bar.njk b/app/views/includes/nav-bar.njk index fc24872a9a..1232c12957 100644 --- a/app/views/includes/nav-bar.njk +++ b/app/views/includes/nav-bar.njk @@ -9,7 +9,7 @@ {% if auth.permission.billRuns %} diff --git a/app/views/macros/bill-run-status-tag.njk b/app/views/macros/bill-run-status-tag.njk index 1b5be516d3..0317be8973 100644 --- a/app/views/macros/bill-run-status-tag.njk +++ b/app/views/macros/bill-run-status-tag.njk @@ -1,24 +1,32 @@ {% from "govuk/components/tag/macro.njk" import govukTag %} -{% macro statusTag(status) %} +{% macro statusTag(status, inline=false) %} {% if status === 'ready' %} - {% set classes = "govuk-tag--blue govuk-!-font-size-27" %} + {% set color = "govuk-tag--blue" %} {% elif status === 'review' %} - {% set classes = "govuk-tag--blue govuk-!-font-size-27" %} + {% set color = "govuk-tag--blue" %} {% elif status === 'sent' %} - {% set classes = "govuk-tag--green govuk-!-font-size-27" %} + {% set color = "govuk-tag--green" %} {% elif status === 'empty' %} - {% set classes = "govuk-tag--grey govuk-!-font-size-27" %} + {% set color = "govuk-tag--grey" %} + {% elif status === 'cancel' %} + {% set color = "govuk-tag--orange" %} {% elif status === 'error' %} - {% set classes = "govuk-tag--red govuk-!-font-size-27" %} + {% set color = "govuk-tag--red" %} {% else %} - {% set classes = "govuk-tag--blue govuk-!-font-size-27" %} + {% set color = "govuk-tag--blue" %} + {% endif %} + + {% if inline %} + {% set fontSize = "" %} + {% else %} + {% set fontSize = "govuk-!-font-size-27" %} {% endif %} {{ govukTag({ text: status, - classes: classes + classes: color + ' ' + fontSize }) }} {% endmacro %} diff --git a/test/controllers/bill-runs-setup.controller.test.js b/test/controllers/bill-runs-setup.controller.test.js index 39cdc76d55..2966b1fbe8 100644 --- a/test/controllers/bill-runs-setup.controller.test.js +++ b/test/controllers/bill-runs-setup.controller.test.js @@ -112,7 +112,7 @@ describe('Bill Runs Setup controller', () => { const response = await server.inject(options) expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/billing/batch/list') + expect(response.headers.location).to.equal('/system/bill-runs') }) }) }) diff --git a/test/controllers/bill-runs.controller.test.js b/test/controllers/bill-runs.controller.test.js index 14ae85e02a..ff9c102343 100644 --- a/test/controllers/bill-runs.controller.test.js +++ b/test/controllers/bill-runs.controller.test.js @@ -11,6 +11,7 @@ const { expect } = Code // Things we need to stub const Boom = require('@hapi/boom') const CancelBillRunService = require('../../app/services/bill-runs/cancel-bill-run.service.js') +const IndexBillRunsService = require('../../app/services/bill-runs/index-bill-runs.service.js') const ReviewLicenceService = require('../../app/services/bill-runs/two-part-tariff/review-licence.service.js') const ReviewBillRunService = require('../../app/services/bill-runs/two-part-tariff/review-bill-run.service.js') const SendBillRunService = require('../../app/services/bill-runs/send-bill-run.service.js') @@ -44,6 +45,61 @@ describe('Bill Runs controller', () => { }) describe('/bill-runs', () => { + describe('GET', () => { + beforeEach(async () => { + options = { + method: 'GET', + url: '/bill-runs?page=2', + auth: { + strategy: 'session', + credentials: { scope: ['billing'] } + } + } + }) + + describe('when the request succeeds', () => { + beforeEach(async () => { + Sinon.stub(IndexBillRunsService, 'go').resolves({ + billRuns: [{ + id: '31fec553-f2de-40cf-a8d7-a5fb65f5761b', + createdAt: '1 January 2024', + link: '/system/bill-runs/31fec553-f2de-40cf-a8d7-a5fb65f5761b', + number: 1002, + numberOfBills: 7, + region: 'Avalon', + scheme: 'sroc', + status: 'ready', + total: '£200.00', + type: 'Supplementary' + }], + busy: 'none', + pageTitle: 'Bill runs (page 2 of 30)', + pagination: { + numberOfPages: 30, + component: { + previous: { href: '/system/bill-runs?page=1' }, + next: { href: '/system/bill-runs?page=3' }, + items: [ + { number: 1, visuallyHiddenText: 'Page 1', href: '/system/bill-runs?page=1', current: false }, + { number: 2, visuallyHiddenText: 'Page 2', href: '/system/bill-runs?page=2', current: true }, + { number: 3, visuallyHiddenText: 'Page 3', href: '/system/bill-runs?page=3', current: false } + ] + } + } + }) + }) + + it('returns the page successfully', async () => { + const response = await server.inject(options) + + expect(response.statusCode).to.equal(200) + expect(response.payload).to.contain('Bill runs (page 2 of 30)') + expect(response.payload).to.contain('Previous') + expect(response.payload).to.contain('Next') + }) + }) + }) + describe('POST', () => { beforeEach(() => { options = _options('POST') @@ -191,7 +247,7 @@ describe('Bill Runs controller', () => { const response = await server.inject(options) expect(response.statusCode).to.equal(302) - expect(response.headers.location).to.equal('/billing/batch/list') + expect(response.headers.location).to.equal('/system/bill-runs') }) }) diff --git a/test/presenters/bill-runs/index.bill-runs.presenter.test.js b/test/presenters/bill-runs/index.bill-runs.presenter.test.js new file mode 100644 index 0000000000..e0f13a5d4e --- /dev/null +++ b/test/presenters/bill-runs/index.bill-runs.presenter.test.js @@ -0,0 +1,118 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') + +const { describe, it, beforeEach } = exports.lab = Lab.script() +const { expect } = Code + +// Thing under test +const IndexBillRunsPresenter = require('../../../app/presenters/bill-runs/index-bill-runs.presenter.js') + +describe('Index Bill Runs presenter', () => { + let billRuns + + describe('when provided with a populated bill run', () => { + beforeEach(() => { + billRuns = _billRuns() + }) + + it('correctly presents the data', () => { + const results = IndexBillRunsPresenter.go(billRuns) + + expect(results).to.equal([ + { + id: '31fec553-f2de-40cf-a8d7-a5fb65f5761b', + createdAt: '1 January 2024', + link: '/system/bill-runs/31fec553-f2de-40cf-a8d7-a5fb65f5761b', + number: 1002, + numberOfBills: 7, + region: 'Avalon', + scheme: 'sroc', + status: 'ready', + total: '£200.00', + type: 'Supplementary' + }, + { + id: 'dfdde4c9-9a0e-440d-b297-7143903c6734', + createdAt: '1 October 2023', + link: '/system/bill-runs/dfdde4c9-9a0e-440d-b297-7143903c6734', + number: 1001, + numberOfBills: 15, + region: 'Albion', + scheme: 'sroc', + status: 'sent', + total: '£300.00', + type: 'Supplementary' + } + ]) + }) + + describe("the 'link' property", () => { + describe("when a bill run has the status 'review'", () => { + beforeEach(() => { + billRuns[0].status = 'review' + }) + + it('generates the href needed to link to the bill run review', () => { + const results = IndexBillRunsPresenter.go(billRuns) + + expect(results[0].link).to.equal('/system/bill-runs/31fec553-f2de-40cf-a8d7-a5fb65f5761b/review') + expect(results[1].link).to.equal('/system/bill-runs/dfdde4c9-9a0e-440d-b297-7143903c6734') + }) + }) + + describe("when a bill run has the status 'cancel'", () => { + beforeEach(() => { + billRuns[0].status = 'cancel' + }) + + it('does not generate a href (returns null)', () => { + const results = IndexBillRunsPresenter.go(billRuns) + + expect(results[0].link).to.be.null() + expect(results[1].link).to.equal('/system/bill-runs/dfdde4c9-9a0e-440d-b297-7143903c6734') + }) + }) + + describe("when a bill run does not have the status 'review'", () => { + it('generates the href needed to link to the bill run', () => { + const results = IndexBillRunsPresenter.go(billRuns) + + expect(results[0].link).to.equal('/system/bill-runs/31fec553-f2de-40cf-a8d7-a5fb65f5761b') + expect(results[1].link).to.equal('/system/bill-runs/dfdde4c9-9a0e-440d-b297-7143903c6734') + }) + }) + }) + }) +}) + +function _billRuns () { + return [ + { + id: '31fec553-f2de-40cf-a8d7-a5fb65f5761b', + batchType: 'supplementary', + billRunNumber: 1002, + createdAt: new Date('2024-01-01'), + netTotal: 20000, + scheme: 'sroc', + status: 'ready', + summer: false, + numberOfBills: 7, + region: 'Avalon' + }, + { + id: 'dfdde4c9-9a0e-440d-b297-7143903c6734', + batchType: 'supplementary', + billRunNumber: 1001, + createdAt: new Date('2023-10-01'), + netTotal: 30000, + scheme: 'sroc', + status: 'sent', + summer: false, + numberOfBills: 15, + region: 'Albion' + } + ] +} diff --git a/test/services/bill-runs/index-bill-runs.service.test.js b/test/services/bill-runs/index-bill-runs.service.test.js new file mode 100644 index 0000000000..399bfb4164 --- /dev/null +++ b/test/services/bill-runs/index-bill-runs.service.test.js @@ -0,0 +1,136 @@ +'use strict' + +// Test framework dependencies +const Lab = require('@hapi/lab') +const Code = require('@hapi/code') +const Sinon = require('sinon') + +const { describe, it, beforeEach, afterEach } = exports.lab = Lab.script() +const { expect } = Code + +// Things we need to stub +const CheckBusyBillRunsService = require('../../../app/services/bill-runs/check-busy-bill-runs.service.js') +const FetchBillRunsService = require('../../../app/services/bill-runs/fetch-bill-runs.service.js') + +// Thing under test +const IndexBillRunsService = require('../../../app/services/bill-runs/index-bill-runs.service.js') + +describe('Index Bill Runs service', () => { + let page + + beforeEach(() => { + // It doesn't matter for these tests what busy state the service returns, only that it returns one. + Sinon.stub(CheckBusyBillRunsService, 'go').resolves('none') + }) + + afterEach(() => { + Sinon.restore() + }) + + describe('when there is only one page of results', () => { + beforeEach(() => { + Sinon.stub(FetchBillRunsService, 'go').resolves({ + results: _fetchedBillRuns(), + total: 2 + }) + }) + + it("returns all bill runs, the state of 'busy' bill runs, the title without page info and no pagination component for the view", async () => { + const result = await IndexBillRunsService.go(page) + + expect(result.billRuns).to.have.length(2) + expect(result.busy).to.equal('none') + expect(result.pageTitle).to.equal('Bill runs') + expect(result.pagination.component).not.to.exist() + }) + }) + + describe('when there are multiple pages of results', () => { + describe('and no page is selected', () => { + beforeEach(() => { + Sinon.stub(FetchBillRunsService, 'go').resolves({ + results: _fetchedBillRuns(), + total: 70 + }) + }) + + it("returns the first page of bill runs, the state of 'busy' bill runs, the title with page info and a pagination component for the view", async () => { + const result = await IndexBillRunsService.go(page) + + expect(result.billRuns).to.have.length(2) + expect(result.busy).to.equal('none') + expect(result.pageTitle).to.equal('Bill runs (page 1 of 3)') + expect(result.pagination.component).to.exist() + }) + }) + + describe("and a page other than 'page 1' with bill runs is selected", () => { + beforeEach(() => { + page = 2 + + Sinon.stub(FetchBillRunsService, 'go').resolves({ + results: _fetchedBillRuns(), + total: 70 + }) + }) + + it("returns the page of bill runs, the state of 'busy' bill runs, the title with page info and a pagination component for the view", async () => { + const result = await IndexBillRunsService.go(page) + + expect(result.billRuns).to.have.length(2) + expect(result.busy).to.equal('none') + expect(result.pageTitle).to.equal('Bill runs (page 2 of 3)') + expect(result.pagination.component).to.exist() + }) + }) + + describe('and a page without bill runs is selected', () => { + beforeEach(() => { + page = 3 + + Sinon.stub(FetchBillRunsService, 'go').resolves({ + results: [], + total: 70 + }) + }) + + it("returns no bill runs, the state of 'busy' bill runs, the title with page info and a pagination component for the view", async () => { + const result = await IndexBillRunsService.go(page) + + expect(result.billRuns).to.be.empty() + expect(result.busy).to.equal('none') + expect(result.pageTitle).to.equal('Bill runs (page 3 of 3)') + expect(result.pagination.component).to.exist() + }) + }) + }) +}) + +function _fetchedBillRuns () { + return [ + { + id: '31fec553-f2de-40cf-a8d7-a5fb65f5761b', + batchType: 'supplementary', + billRunNumber: 1002, + createdAt: new Date('2024-01-01'), + netTotal: 20000, + scheme: 'sroc', + status: 'ready', + summer: false, + numberOfBills: 7, + region: 'Avalon' + }, + { + id: 'dfdde4c9-9a0e-440d-b297-7143903c6734', + batchType: 'supplementary', + billRunNumber: 1001, + createdAt: new Date('2023-10-01'), + netTotal: 30000, + scheme: 'sroc', + status: 'sent', + summer: false, + numberOfBills: 15, + region: 'Albion' + } + ] +}