Skip to content

Commit

Permalink
more tests for various kinds of errors, regroup errors in same module
Browse files Browse the repository at this point in the history
This mostly improves testing and code organization, it does not fix anything
  • Loading branch information
jahow committed Nov 9, 2024
1 parent 4f311f2 commit 683cb05
Show file tree
Hide file tree
Showing 10 changed files with 267 additions and 112 deletions.
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ export {
} from './shared/http-utils.js';
export {
check,
default as ServiceExceptionError,
} from './shared/service-exception-error.js';
ServiceExceptionError,
EndpointError,
} from './shared/errors.js';

export { enableFallbackWithoutWorker } from './worker/index.js';
import './worker-fallback/index.js';
4 changes: 2 additions & 2 deletions src/ogc-api/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import {
checkStyleConformance,
checkTileConformance,
parseBaseCollectionInfo,
parseFullStyleInfo,
parseBasicStyleInfo,
parseCollectionParameters,
parseCollections,
parseConformance,
parseEndpointInfo,
parseBasicStyleInfo,
parseFullStyleInfo,
parseTileMatrixSets,
} from './info.js';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import wmsException110 from '../../fixtures/wms/service-exception-report-1-1-0.x
import wmsException111 from '../../fixtures/wms/service-exception-report-1-1-1.xml';
// @ts-expect-error ts-migrate(7016)
import wmsException130 from '../../fixtures/wms/service-exception-report-1-3-0.xml';
import ServiceExceptionError, {
check,
parse,
} from './service-exception-error.js';
import { check, parse, ServiceExceptionError } from './errors.js';
import {
findChildElement,
getRootElement,
Expand Down
93 changes: 90 additions & 3 deletions src/shared/errors.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,96 @@
import type { XmlDocument, XmlElement } from '@rgrove/parse-xml';
import {
findChildElement,
getElementAttribute,
getElementName,
getElementText,
getRootElement,
stripNamespace,
} from '../shared/xml-utils.js';

export class EndpointError extends Error {
constructor(
public message: string,
public httpStatus?: number,
public isCrossOriginRelated?: boolean
message: string,
public readonly httpStatus?: number,
public readonly isCrossOriginRelated?: boolean
) {
super(message);
}
}

/**
* Representation of an Exception reported by an OWS service
*
* This is usually contained in a ServiceExceptionReport or ExceptionReport
* document and represented as a ServiceException or Exception element
*/
export class ServiceExceptionError extends Error {
/**
* Constructor
* @param message Error message
* @param requestUrl URL which resulted in the ServiceException
* @param code Optional ServiceException code
* @param locator Optional ServiceException locator
* @param response Optional response content received
*/
public constructor(
message: string,
public readonly requestUrl?: string,
public readonly code?: string,
public readonly locator?: string,
public readonly response?: XmlDocument
) {
super(message);
}
}

/**
* Parse a ServiceException element to a ServiceExceptionError
* @param serviceException ServiceException element
* @param url URL from which the ServiceException was generated
*/
export function parse(
serviceException: XmlElement,
url?: string
): ServiceExceptionError {
const errorCode =
getElementAttribute(serviceException, 'code') ||
getElementAttribute(serviceException, 'exceptionCode');
const errorLocator = getElementAttribute(serviceException, 'locator');
const textElement =
findChildElement(serviceException, 'ExceptionText') || serviceException;
const errorMessage = getElementText(textElement).trim();
return new ServiceExceptionError(
errorMessage,
url,
errorCode,
errorLocator,
serviceException.document
);
}

/**
* Check the response for a ServiceExceptionReport and if present throw one
* @param response Response to check
* @param url URL from which response was generated
*/
export function check(response: XmlDocument, url?: string): XmlDocument {
const rootEl = getRootElement(response);
const rootElName = stripNamespace(getElementName(rootEl));
if (rootElName === 'ServiceExceptionReport') {
// document contains a ServiceExceptionReport, so generate an Error from
// the first ServiceException contained in it
const error = findChildElement(rootEl, 'ServiceException');
if (error) {
throw parse(error, url);
}
}
if (rootElName === 'ExceptionReport') {
const error = findChildElement(rootEl, 'Exception');
if (error) {
throw parse(error, url);
}
}
// there was nothing to convert to an Error so just pass the document on
return response;
}
2 changes: 1 addition & 1 deletion src/shared/http-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function queryXmlDocument(url: string) {
);
})
)
.then(async (resp) => {
.then(async (resp: Response) => {
if (!resp.ok) {
const text = await resp.text();
throw new EndpointError(
Expand Down
86 changes: 0 additions & 86 deletions src/shared/service-exception-error.ts

This file was deleted.

79 changes: 77 additions & 2 deletions src/wfs/endpoint.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import capabilitiesStates from '../../fixtures/wfs/capabilities-states-2-0-0.xml
import exceptionReportWms from '../../fixtures/wfs/exception-report-wms.xml';
import WfsEndpoint from './endpoint.js';
import { useCache } from '../shared/cache.js';
import { EndpointError, ServiceExceptionError } from '../shared/errors.js';

jest.mock('../shared/cache', () => ({
useCache: jest.fn((factory) => factory()),
Expand All @@ -28,6 +29,7 @@ describe('WfsEndpoint', () => {
});

beforeEach(() => {
global.fetchPreHandler = () => {};
global.fetchResponseFactory = (url) => {
if (url.indexOf('GetCapabilities') > -1) return capabilities200;
if (url.indexOf('GetFeature') > -1) {
Expand Down Expand Up @@ -74,15 +76,88 @@ describe('WfsEndpoint', () => {
await expect(endpoint.isReady()).resolves.toEqual(endpoint);
});

describe('CORS error handling', () => {
beforeEach(() => {
global.fetchPreHandler = (url, options) => {
if (options?.method === 'HEAD') return 'ok!';
throw new Error('CORS problem');
};
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error.constructor.name).toBe('EndpointError');
expect(error.message).toBe(
'The document could not be fetched due to CORS limitations'
);
expect(error.httpStatus).toBe(0);
expect(error.isCrossOriginRelated).toBe(true);
});
});

describe('endpoint error handling', () => {
beforeEach(() => {
global.fetchPreHandler = () => {
throw new TypeError('other kind of problem');
};
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error.constructor.name).toBe('EndpointError');
expect(error.message).toBe(
'Fetching the document failed either due to network errors or unreachable host, error is: other kind of problem'
);
expect(error.httpStatus).toBe(0);
expect(error.isCrossOriginRelated).toBe(false);
});
});

describe('http error handling', () => {
beforeEach(() => {
global.fetchPreHandler = () => ({
ok: false,
text: () => Promise.resolve('something broke in the server'),
status: 500,
statusText: 'Internal Server Error',
});
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects with a relevant error', async () => {
const error = (await endpoint
.isReady()
.catch((e) => e)) as EndpointError;
expect(error.constructor.name).toBe('EndpointError');
expect(error.message).toBe(
'Received an error with code 500: something broke in the server'
);
expect(error.httpStatus).toBe(500);
expect(error.isCrossOriginRelated).toBe(false);
});
});

describe('service exception handling', () => {
beforeEach(() => {
global.fetchResponseFactory = () => exceptionReportWms;
endpoint = new WfsEndpoint('https://my.test.service/ogc/wms');
endpoint = new WfsEndpoint('https://my.test.service/ogc/wfs');
});
it('rejects when the endpoint returns an exception report', async () => {
await expect(endpoint.isReady()).rejects.toThrow(
const error = (await endpoint
.isReady()
.catch((e) => e)) as ServiceExceptionError;
expect(error.constructor.name).toBe('ServiceExceptionError');
expect(error.message).toBe(
'msWFSDispatch(): WFS server error. WFS request not enabled. Check wfs/ows_enable_request settings.'
);
expect(error.requestUrl).toBe(
'https://my.test.service/ogc/wfs?SERVICE=WFS&REQUEST=GetCapabilities'
);
expect(error.code).toBe('InvalidParameterValue');
expect(error.locator).toBe('request');
});
});
});
Expand Down
Loading

0 comments on commit 683cb05

Please sign in to comment.