Skip to content
Draft
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
153 changes: 153 additions & 0 deletions __tests__/pages/apply/address-history.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { configureStore } from '@reduxjs/toolkit';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { type ReactNode } from 'react';
import { Provider } from 'react-redux';

import { lookUpAddress } from '../../../lib/gateways/internal-api';
import { ApiCallStatusCode } from '../../../lib/store/apiCallsStatus';
import AddressHistoryPage from '../../../pages/apply/[resident]/address-history';
import { generateApplication } from '../../../testUtils/applicationHelper';

const personId = 'person-1';
const application = generateApplication('app-1', personId, true, false);

const lookUpAddressMock = lookUpAddress as jest.MockedFunction<
typeof lookUpAddress
>;

jest.mock('next/router', () => ({
useRouter: () => ({
push: jest.fn(),
query: { resident: 'person-1' },
}),
}));

jest.mock('../../../lib/gateways/internal-api', () => ({
lookUpAddress: jest.fn(),
}));

jest.mock('../../../components/application/ApplicantStep', () => ({
__esModule: true,
default: ({ children }: { children: ReactNode }) => <>{children}</>,
}));

const foundAddress = {
UPRN: 123,
line1: '1 Test Street',
line2: '',
line3: '',
line4: '',
postcode: 'E9 6PT',
town: 'London',
};

const renderPage = () => {
const store = configureStore({
reducer: {
application: (state = application) => state,
hrApiCallsStatus: (
state = {
updateApplication: {
callStatus: ApiCallStatusCode.IDLE,
error: null,
},
},
) => state,
},
});

return render(
<Provider store={store}>
<AddressHistoryPage />
</Provider>,
);
};

const submitPostcode = (postcode = 'E9 6PT') => {
fireEvent.change(screen.getByLabelText('Postcode'), {
target: { value: postcode },
});
fireEvent.click(
screen.getByTestId(
'test-apply-resident-address-history-find-address-button',
),
);
};

describe('Apply resident address history page', () => {
afterEach(() => {
lookUpAddressMock.mockReset();
});

it('falls back to manual entry when lookup throws', async () => {
const consoleError = jest.spyOn(console, 'error').mockImplementation();
lookUpAddressMock.mockRejectedValue(
new Error('Unable to look up address (500)'),
);

renderPage();
submitPostcode('not a UK postcode');

expect(
await screen.findByRole('heading', { name: 'What is your address?' }),
).toBeInTheDocument();
expect(
screen.queryByLabelText('Select an address'),
).not.toBeInTheDocument();
expect(consoleError).not.toHaveBeenCalled();
consoleError.mockRestore();
});

it('falls back to manual entry when lookup returns no address list', async () => {
lookUpAddressMock.mockResolvedValue({
page_count: 0,
total_count: 0,
} as never);

renderPage();
submitPostcode();

expect(
await screen.findByRole('heading', { name: 'What is your address?' }),
).toBeInTheDocument();
expect(
screen.queryByLabelText('Select an address'),
).not.toBeInTheDocument();
});

it('falls back to manual entry when lookup returns an empty address list', async () => {
lookUpAddressMock.mockResolvedValue({
address: [],
page_count: 0,
total_count: 0,
});

renderPage();
submitPostcode();

expect(
await screen.findByRole('heading', { name: 'What is your address?' }),
).toBeInTheDocument();
});

it('shows the address list when lookup returns matches', async () => {
lookUpAddressMock.mockResolvedValue({
address: [foundAddress],
page_count: 1,
total_count: 1,
});

renderPage();
submitPostcode();

expect(
await screen.findByLabelText('Select an address'),
).toBeInTheDocument();
expect(
screen.queryByRole('heading', { name: 'What is your address?' }),
).not.toBeInTheDocument();
await waitFor(() => {
expect(lookUpAddressMock).toHaveBeenCalledWith('E9 6PT');
});
});
});
23 changes: 23 additions & 0 deletions cypress/e2e/pages/apply/[resident]/address-history.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,27 @@ describe('Apply resident address history page', () => {
ApplyResidentAddressHistoryPage.getGetSaveAndContinueButton().click();
cy.contains('Unable to update application (409)');
});

it('falls back to manual address entry when postcode lookup fails', () => {
cy.intercept('GET', '/api/address/*', {
statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
body: { message: 'Unable to look up address' },
}).as('failedAddressLookup');

ApplyHouseholdPage.visit();
ApplyHouseholdPage.getContinueToNextStepLink().scrollIntoView().click();
ApplyExpectPage.getContinueToNextStepButton().click();
ApplyOverviewPage.getApplicantButton(personId).click();
ApplyResidentIndexPage.getAddressHistorySectionLink().click();

ApplyResidentAddressHistoryPage.getPostcodeInputField().type(
'not a UK postcode',
);
ApplyResidentAddressHistoryPage.getFindAddressButton().click();
cy.wait('@failedAddressLookup');
ApplyResidentAddressHistoryPage.getManualAddressHeading().should(
'be.visible',
);
cy.contains('Select an address').should('not.exist');
});
});
4 changes: 4 additions & 0 deletions cypress/pages/apply/[resident]/address-history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class ApplyResidentAddressHistoryPage {
static getMovingDateYear() {
return cy.get('#date-year');
}

static getManualAddressHeading() {
return cy.contains('What is your address?');
}
}

export default ApplyResidentAddressHistoryPage;
42 changes: 41 additions & 1 deletion lib/gateways/internal-api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Application } from '../../domain/HousingApi';
import { CreateApplicationError, createApplication } from './internal-api';
import {
CreateApplicationError,
createApplication,
lookUpAddress,
} from './internal-api';

const application = { id: 'app-1' } as Application;

Expand Down Expand Up @@ -88,3 +92,39 @@ describe('createApplication', () => {
consoleError.mockRestore();
});
});

describe('lookUpAddress', () => {
afterEach(() => {
jest.restoreAllMocks();
});

it('returns lookup results when the request succeeds', async () => {
const result = {
address: [{ UPRN: 1, line1: '1 Test Street' }],
page_count: 1,
total_count: 1,
};
mockFetch({
ok: true,
status: 200,
json: async () => result,
});

await expect(lookUpAddress('E9 6PT')).resolves.toEqual(result);
expect(global.fetch).toHaveBeenCalledWith('/api/address/E9%206PT', {
method: 'GET',
});
});

it('throws when the lookup API returns an error body without addresses', async () => {
mockFetch({
ok: false,
status: 500,
json: async () => ({ message: 'Unable to look up address' }),
});

await expect(lookUpAddress('not a UK postcode')).rejects.toThrow(
'Unable to look up address (500)',
);
});
});
6 changes: 5 additions & 1 deletion lib/gateways/internal-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,14 @@
};

export const lookUpAddress = async (postCode: string) => {
const res = await fetch(`/api/address/${postCode}`, {
const res = await fetch(`/api/address/${encodeURIComponent(postCode)}`, {
method: 'GET',
});

if (!res.ok) {
throw Error(`Unable to look up address (${res.status})`);

Check warning on line 49 in lib/gateways/internal-api.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use `new Error()` instead of `Error()`.

See more on https://sonarcloud.io/project/issues?id=LBHackney-IT_lbh-housing-register&issues=AaCGO1QZyaqWZM1RUoxQ&open=AaCGO1QZyaqWZM1RUoxQ&pullRequest=583
}

return (await res.json()) as AddressLookupResult;
};

Expand Down
12 changes: 8 additions & 4 deletions pages/apply/[resident]/address-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,14 +294,18 @@ const ApplicationStep = (): JSX.Element => {
case 'postcode-entry':
try {
const r = await lookUpAddress(values.postcode);
setPostcodeResults(r.address);
const addresses = r.address ?? [];
if (addresses.length === 0) {
setState('manual-entry');
break;
}
setPostcodeResults(addresses);
formikHelpers.setValues({
...values,
uprn: r.address[0]?.UPRN.toString(),
uprn: addresses[0].UPRN.toString(),
});
setState('choose-address');
} catch (e) {
console.error(e);
} catch {
setState('manual-entry');
}

Expand Down