@dhis2/app-service-data 3.16.0 → 3.17.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/__tests__/integration.test.js +10 -16
- package/build/cjs/__tests__/mutations.test.js +5 -8
- package/build/cjs/index.js +16 -44
- package/build/cjs/react/components/CustomDataProvider.js +9 -11
- package/build/cjs/react/components/DataMutation.js +7 -8
- package/build/cjs/react/components/DataProvider.js +3 -4
- package/build/cjs/react/components/DataProvider.test.js +3 -4
- package/build/cjs/react/components/DataQuery.js +8 -9
- package/build/cjs/react/components/index.js +33 -0
- package/build/cjs/react/context/DataContext.js +2 -2
- package/build/cjs/react/context/defaultDataContext.js +13 -0
- package/build/cjs/react/context/{defaultContext.test.js → defaultDataContext.test.js} +3 -3
- package/build/cjs/react/hooks/index.js +26 -0
- package/build/cjs/react/hooks/useDataMutation.js +6 -7
- package/build/cjs/react/hooks/useDataMutation.test.js +44 -71
- package/build/cjs/react/hooks/useDataQuery.js +10 -14
- package/build/cjs/react/hooks/useDataQuery.test.js +172 -265
- package/build/cjs/react/hooks/useQueryExecutor.js +11 -13
- package/build/cjs/react/hooks/useQueryExecutor.test.js +12 -16
- package/build/cjs/react/hooks/useStaticInput.js +4 -5
- package/build/cjs/react/hooks/useStaticInput.test.js +24 -39
- package/build/cjs/react/index.js +22 -77
- package/build/es/__tests__/integration.test.js +10 -16
- package/build/es/__tests__/mutations.test.js +5 -8
- package/build/es/index.js +2 -3
- package/build/es/react/components/CustomDataProvider.js +7 -9
- package/build/es/react/components/DataMutation.js +7 -8
- package/build/es/react/components/DataProvider.js +1 -2
- package/build/es/react/components/DataProvider.test.js +1 -2
- package/build/es/react/components/DataQuery.js +8 -9
- package/build/es/react/components/index.js +4 -0
- package/build/es/react/context/DataContext.js +2 -2
- package/build/es/react/context/{defaultContext.js → defaultDataContext.js} +2 -3
- package/build/es/react/context/{defaultContext.test.js → defaultDataContext.test.js} +3 -3
- package/build/es/react/hooks/index.js +3 -0
- package/build/es/react/hooks/useDataMutation.js +6 -7
- package/build/es/react/hooks/useDataMutation.test.js +44 -71
- package/build/es/react/hooks/useDataQuery.js +10 -14
- package/build/es/react/hooks/useDataQuery.test.js +172 -265
- package/build/es/react/hooks/useQueryExecutor.js +10 -12
- package/build/es/react/hooks/useQueryExecutor.test.js +12 -16
- package/build/es/react/hooks/useStaticInput.js +4 -5
- package/build/es/react/hooks/useStaticInput.test.js +24 -39
- package/build/es/react/index.js +3 -11
- package/build/types/index.d.ts +2 -3
- package/build/types/react/components/CustomDataProvider.d.ts +1 -1
- package/build/types/react/components/DataMutation.d.ts +1 -1
- package/build/types/react/components/DataQuery.d.ts +1 -1
- package/build/types/react/components/index.d.ts +4 -0
- package/build/types/react/context/defaultDataContext.d.ts +4 -0
- package/build/types/react/hooks/index.d.ts +3 -0
- package/build/types/react/hooks/mergeAndCompareVariables.d.ts +1 -1
- package/build/types/react/hooks/useDataEngine.d.ts +1 -1
- package/build/types/react/hooks/useDataMutation.d.ts +1 -1
- package/build/types/react/hooks/useDataQuery.d.ts +1 -1
- package/build/types/react/index.d.ts +2 -11
- package/build/types/types.d.ts +2 -7
- package/package.json +4 -3
- package/build/cjs/engine/DataEngine.js +0 -73
- package/build/cjs/engine/DataEngine.test.js +0 -156
- package/build/cjs/engine/helpers/getMutationFetchType.js +0 -8
- package/build/cjs/engine/helpers/getMutationFetchType.test.js +0 -39
- package/build/cjs/engine/helpers/resolveDynamicQuery.js +0 -21
- package/build/cjs/engine/helpers/resolveDynamicQuery.test.js +0 -63
- package/build/cjs/engine/helpers/validate.js +0 -62
- package/build/cjs/engine/helpers/validate.test.js +0 -206
- package/build/cjs/engine/index.js +0 -104
- package/build/cjs/engine/types/DataEngineLink.js +0 -1
- package/build/cjs/engine/types/ExecuteOptions.js +0 -1
- package/build/cjs/engine/types/FetchError.js +0 -24
- package/build/cjs/engine/types/FetchError.test.js +0 -14
- package/build/cjs/engine/types/InvalidQueryError.js +0 -18
- package/build/cjs/engine/types/JsonValue.js +0 -1
- package/build/cjs/engine/types/Mutation.js +0 -1
- package/build/cjs/engine/types/PossiblyDynamic.js +0 -1
- package/build/cjs/engine/types/Query.js +0 -1
- package/build/cjs/engine/types/QueryParameters.js +0 -1
- package/build/cjs/links/CustomDataLink.js +0 -51
- package/build/cjs/links/CustomDataLink.test.js +0 -73
- package/build/cjs/links/ErrorLink.js +0 -20
- package/build/cjs/links/RestAPILink/fetchData.js +0 -80
- package/build/cjs/links/RestAPILink/fetchData.test.js +0 -132
- package/build/cjs/links/RestAPILink/metadataResources.js +0 -22
- package/build/cjs/links/RestAPILink/path.js +0 -14
- package/build/cjs/links/RestAPILink/path.test.js +0 -16
- package/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +0 -58
- package/build/cjs/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +0 -73
- package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.js +0 -80
- package/build/cjs/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +0 -120
- package/build/cjs/links/RestAPILink/queryToRequestOptions/textPlainMatchers.js +0 -170
- package/build/cjs/links/RestAPILink/queryToRequestOptions/textPlainMatchers.test.js +0 -246
- package/build/cjs/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.js +0 -14
- package/build/cjs/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.test.js +0 -20
- package/build/cjs/links/RestAPILink/queryToRequestOptions.js +0 -34
- package/build/cjs/links/RestAPILink/queryToRequestOptions.test.js +0 -107
- package/build/cjs/links/RestAPILink/queryToResourcePath.js +0 -82
- package/build/cjs/links/RestAPILink/queryToResourcePath.test.js +0 -173
- package/build/cjs/links/RestAPILink/validateQuery.js +0 -59
- package/build/cjs/links/RestAPILink/validateQuery.test.js +0 -209
- package/build/cjs/links/RestAPILink.js +0 -33
- package/build/cjs/links/RestAPILink.test.js +0 -21
- package/build/cjs/links/index.js +0 -38
- package/build/cjs/locales/en/translations.json +0 -3
- package/build/cjs/locales/index.js +0 -21
- package/build/cjs/react/context/defaultContext.js +0 -14
- package/build/es/engine/DataEngine.js +0 -66
- package/build/es/engine/DataEngine.test.js +0 -154
- package/build/es/engine/helpers/getMutationFetchType.js +0 -1
- package/build/es/engine/helpers/getMutationFetchType.test.js +0 -37
- package/build/es/engine/helpers/resolveDynamicQuery.js +0 -14
- package/build/es/engine/helpers/resolveDynamicQuery.test.js +0 -61
- package/build/es/engine/helpers/validate.js +0 -53
- package/build/es/engine/helpers/validate.test.js +0 -204
- package/build/es/engine/index.js +0 -10
- package/build/es/engine/types/DataEngineLink.js +0 -1
- package/build/es/engine/types/ExecuteOptions.js +0 -1
- package/build/es/engine/types/FetchError.js +0 -17
- package/build/es/engine/types/FetchError.test.js +0 -12
- package/build/es/engine/types/InvalidQueryError.js +0 -11
- package/build/es/engine/types/JsonValue.js +0 -1
- package/build/es/engine/types/Mutation.js +0 -1
- package/build/es/engine/types/PossiblyDynamic.js +0 -1
- package/build/es/engine/types/Query.js +0 -1
- package/build/es/engine/types/QueryParameters.js +0 -1
- package/build/es/links/CustomDataLink.js +0 -44
- package/build/es/links/CustomDataLink.test.js +0 -71
- package/build/es/links/ErrorLink.js +0 -13
- package/build/es/links/RestAPILink/fetchData.js +0 -71
- package/build/es/links/RestAPILink/fetchData.test.js +0 -130
- package/build/es/links/RestAPILink/metadataResources.js +0 -16
- package/build/es/links/RestAPILink/path.js +0 -7
- package/build/es/links/RestAPILink/path.test.js +0 -14
- package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.js +0 -47
- package/build/es/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.test.js +0 -71
- package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.js +0 -70
- package/build/es/links/RestAPILink/queryToRequestOptions/requestContentType.test.js +0 -118
- package/build/es/links/RestAPILink/queryToRequestOptions/textPlainMatchers.js +0 -151
- package/build/es/links/RestAPILink/queryToRequestOptions/textPlainMatchers.test.js +0 -244
- package/build/es/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.js +0 -7
- package/build/es/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.test.js +0 -18
- package/build/es/links/RestAPILink/queryToRequestOptions.js +0 -27
- package/build/es/links/RestAPILink/queryToRequestOptions.test.js +0 -105
- package/build/es/links/RestAPILink/queryToResourcePath.js +0 -75
- package/build/es/links/RestAPILink/queryToResourcePath.test.js +0 -171
- package/build/es/links/RestAPILink/validateQuery.js +0 -52
- package/build/es/links/RestAPILink/validateQuery.test.js +0 -207
- package/build/es/links/RestAPILink.js +0 -26
- package/build/es/links/RestAPILink.test.js +0 -19
- package/build/es/links/index.js +0 -4
- package/build/es/locales/en/translations.json +0 -3
- package/build/es/locales/index.js +0 -13
- package/build/types/engine/DataEngine.d.ts +0 -13
- package/build/types/engine/helpers/getMutationFetchType.d.ts +0 -3
- package/build/types/engine/helpers/resolveDynamicQuery.d.ts +0 -2
- package/build/types/engine/helpers/validate.d.ts +0 -4
- package/build/types/engine/index.d.ts +0 -9
- package/build/types/engine/types/DataEngineLink.d.ts +0 -9
- package/build/types/engine/types/ExecuteOptions.d.ts +0 -9
- package/build/types/engine/types/FetchError.d.ts +0 -19
- package/build/types/engine/types/InvalidQueryError.d.ts +0 -5
- package/build/types/engine/types/JsonValue.d.ts +0 -6
- package/build/types/engine/types/Mutation.d.ts +0 -29
- package/build/types/engine/types/PossiblyDynamic.d.ts +0 -1
- package/build/types/engine/types/Query.d.ts +0 -24
- package/build/types/engine/types/QueryParameters.d.ts +0 -12
- package/build/types/links/CustomDataLink.d.ts +0 -17
- package/build/types/links/ErrorLink.d.ts +0 -6
- package/build/types/links/RestAPILink/fetchData.d.ts +0 -4
- package/build/types/links/RestAPILink/metadataResources.d.ts +0 -2
- package/build/types/links/RestAPILink/path.d.ts +0 -1
- package/build/types/links/RestAPILink/queryToRequestOptions/multipartFormDataMatchers.d.ts +0 -6
- package/build/types/links/RestAPILink/queryToRequestOptions/requestContentType.d.ts +0 -6
- package/build/types/links/RestAPILink/queryToRequestOptions/textPlainMatchers.d.ts +0 -14
- package/build/types/links/RestAPILink/queryToRequestOptions/xWwwFormUrlencodedMatchers.d.ts +0 -2
- package/build/types/links/RestAPILink/queryToRequestOptions.d.ts +0 -2
- package/build/types/links/RestAPILink/queryToResourcePath.d.ts +0 -3
- package/build/types/links/RestAPILink/validateQuery.d.ts +0 -2
- package/build/types/links/RestAPILink.d.ts +0 -10
- package/build/types/links/index.d.ts +0 -3
- package/build/types/react/context/defaultContext.d.ts +0 -4
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { CustomDataLink } from './CustomDataLink';
|
|
2
|
-
describe('CustomDataLink', () => {
|
|
3
|
-
it('Should return mocked resource', async () => {
|
|
4
|
-
const link = new CustomDataLink({
|
|
5
|
-
foo: 'bar'
|
|
6
|
-
});
|
|
7
|
-
expect(link.executeResourceQuery('read', {
|
|
8
|
-
resource: 'foo'
|
|
9
|
-
}, {})).resolves.toBe('bar');
|
|
10
|
-
});
|
|
11
|
-
it('Should throw error on mock miss', async () => {
|
|
12
|
-
const link = new CustomDataLink({
|
|
13
|
-
foo: 'bar'
|
|
14
|
-
});
|
|
15
|
-
expect(link.executeResourceQuery('read', {
|
|
16
|
-
resource: 'something'
|
|
17
|
-
}, {})).rejects.toMatchInlineSnapshot(`[Error: No data provided for resource type something!]`);
|
|
18
|
-
});
|
|
19
|
-
it('Should swallow miss error with failOnMiss=false', async () => {
|
|
20
|
-
const link = new CustomDataLink({
|
|
21
|
-
foo: 'bar'
|
|
22
|
-
}, {
|
|
23
|
-
failOnMiss: false
|
|
24
|
-
});
|
|
25
|
-
expect(link.executeResourceQuery('read', {
|
|
26
|
-
resource: 'something'
|
|
27
|
-
}, {})).resolves.toBe(null);
|
|
28
|
-
});
|
|
29
|
-
it('Should resolve functional resource', async () => {
|
|
30
|
-
const link = new CustomDataLink({
|
|
31
|
-
foo: async () => 'bar'
|
|
32
|
-
});
|
|
33
|
-
expect(link.executeResourceQuery('read', {
|
|
34
|
-
resource: 'foo'
|
|
35
|
-
}, {})).resolves.toBe('bar');
|
|
36
|
-
});
|
|
37
|
-
it('Should throw if resolves to undefined', async () => {
|
|
38
|
-
const link = new CustomDataLink({
|
|
39
|
-
foo: async () => undefined
|
|
40
|
-
});
|
|
41
|
-
expect(link.executeResourceQuery('read', {
|
|
42
|
-
resource: 'foo'
|
|
43
|
-
}, {})).rejects.toMatchInlineSnapshot(`[Error: The custom function for resource foo must always return a value but returned undefined]`);
|
|
44
|
-
});
|
|
45
|
-
it('Should swallow functional miss if failOnMiss=false', async () => {
|
|
46
|
-
const link = new CustomDataLink({
|
|
47
|
-
foo: async () => undefined
|
|
48
|
-
}, {
|
|
49
|
-
failOnMiss: false
|
|
50
|
-
});
|
|
51
|
-
expect(link.executeResourceQuery('read', {
|
|
52
|
-
resource: 'foo'
|
|
53
|
-
}, {})).resolves.toBe(null);
|
|
54
|
-
});
|
|
55
|
-
it('Should wait forever with loadForever=true', async () => {
|
|
56
|
-
jest.useFakeTimers();
|
|
57
|
-
const link = new CustomDataLink({}, {
|
|
58
|
-
loadForever: true
|
|
59
|
-
});
|
|
60
|
-
let done = false;
|
|
61
|
-
link.executeResourceQuery('read', {
|
|
62
|
-
resource: 'foo'
|
|
63
|
-
}, {}).then(() => {
|
|
64
|
-
done = true;
|
|
65
|
-
}).catch(() => {
|
|
66
|
-
done = true;
|
|
67
|
-
});
|
|
68
|
-
jest.advanceTimersByTime(100);
|
|
69
|
-
expect(done).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
2
|
-
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
3
|
-
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
|
-
export class ErrorLink {
|
|
5
|
-
constructor(errorMessage) {
|
|
6
|
-
_defineProperty(this, "errorMessage", void 0);
|
|
7
|
-
this.errorMessage = errorMessage;
|
|
8
|
-
}
|
|
9
|
-
executeResourceQuery() {
|
|
10
|
-
console.error(this.errorMessage);
|
|
11
|
-
return Promise.reject(this.errorMessage);
|
|
12
|
-
}
|
|
13
|
-
}
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { FetchError } from '../../engine';
|
|
2
|
-
export const parseContentType = contentType => contentType ? contentType.split(';')[0].trim().toLowerCase() : '';
|
|
3
|
-
export const parseStatus = async response => {
|
|
4
|
-
const accessError = response.status === 401 || response.status === 403 || response.status === 409;
|
|
5
|
-
if (accessError) {
|
|
6
|
-
let message;
|
|
7
|
-
let details = {};
|
|
8
|
-
try {
|
|
9
|
-
details = await response.json();
|
|
10
|
-
message = details.message;
|
|
11
|
-
} catch (e) {
|
|
12
|
-
// Do nothing
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Set a message in case of invalid json, or json without 'message' property
|
|
16
|
-
if (!message) {
|
|
17
|
-
message = response.status === 401 ? 'Unauthorized' : 'Forbidden';
|
|
18
|
-
}
|
|
19
|
-
throw new FetchError({
|
|
20
|
-
type: 'access',
|
|
21
|
-
message,
|
|
22
|
-
details
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
if (response.status < 200 || response.status >= 400) {
|
|
26
|
-
const message = `An unknown error occurred - ${response.statusText} (${response.status})`;
|
|
27
|
-
let details = {};
|
|
28
|
-
try {
|
|
29
|
-
details = await response.json();
|
|
30
|
-
} catch (e) {
|
|
31
|
-
// We can leave details as is if parsing fails
|
|
32
|
-
}
|
|
33
|
-
throw new FetchError({
|
|
34
|
-
type: 'unknown',
|
|
35
|
-
message,
|
|
36
|
-
details
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
return response;
|
|
40
|
-
};
|
|
41
|
-
export function fetchData(url) {
|
|
42
|
-
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
43
|
-
return fetch(url, {
|
|
44
|
-
...options,
|
|
45
|
-
credentials: 'include',
|
|
46
|
-
headers: {
|
|
47
|
-
'X-Requested-With': 'XMLHttpRequest',
|
|
48
|
-
Accept: 'application/json',
|
|
49
|
-
...options.headers
|
|
50
|
-
}
|
|
51
|
-
}).catch(err => {
|
|
52
|
-
throw new FetchError({
|
|
53
|
-
type: 'network',
|
|
54
|
-
message: 'An unknown network error occurred',
|
|
55
|
-
details: err
|
|
56
|
-
});
|
|
57
|
-
}).then(parseStatus).then(async response => {
|
|
58
|
-
const contentType = parseContentType(response.headers.get('Content-Type'));
|
|
59
|
-
|
|
60
|
-
// 'application/json'
|
|
61
|
-
if (contentType === 'application/json') {
|
|
62
|
-
return await response.json(); // Will throw if invalid JSON!
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// 'text/*'
|
|
66
|
-
if (/^text\/[a-z0-9.-]+$/.test(contentType)) {
|
|
67
|
-
return await response.text();
|
|
68
|
-
}
|
|
69
|
-
return await response.blob();
|
|
70
|
-
});
|
|
71
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { FetchError } from '../../engine';
|
|
2
|
-
import { parseStatus, fetchData, parseContentType } from './fetchData';
|
|
3
|
-
describe('networkFetch', () => {
|
|
4
|
-
describe('parseContentType', () => {
|
|
5
|
-
it('should pass through simple content-types', () => {
|
|
6
|
-
expect(parseContentType('text/html')).toBe('text/html');
|
|
7
|
-
expect(parseContentType('text/plain')).toBe('text/plain');
|
|
8
|
-
expect(parseContentType('application/vnd.api+json')).toBe('application/vnd.api+json');
|
|
9
|
-
});
|
|
10
|
-
it('should strip parameters', () => {
|
|
11
|
-
expect(parseContentType('text/svg+xml;charset=utf-8')).toBe('text/svg+xml');
|
|
12
|
-
expect(parseContentType('text/html;testing123')).toBe('text/html');
|
|
13
|
-
});
|
|
14
|
-
it('should trim type', () => {
|
|
15
|
-
expect(parseContentType(' text/xml ')).toBe('text/xml');
|
|
16
|
-
expect(parseContentType(' application/json ; charset = utf-8')).toBe('application/json');
|
|
17
|
-
});
|
|
18
|
-
it('should convert to lower-case', () => {
|
|
19
|
-
expect(parseContentType(' Text/XML ')).toBe('text/xml');
|
|
20
|
-
expect(parseContentType('application/JSON ; charset = UTF-8')).toBe('application/json');
|
|
21
|
-
});
|
|
22
|
-
it('should correctly parse application/json with charset param', () => {
|
|
23
|
-
expect(parseContentType('application/json;charset=UTF-8')).toBe('application/json');
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
describe('parseStatus', () => {
|
|
27
|
-
it('should pass through the response for a success status code', async () => {
|
|
28
|
-
const response = {
|
|
29
|
-
status: 200
|
|
30
|
-
};
|
|
31
|
-
await expect(parseStatus(response)).resolves.toBe(response);
|
|
32
|
-
});
|
|
33
|
-
it('should throw an access error for 401, 403 and 409 errors', async () => {
|
|
34
|
-
const response401 = {
|
|
35
|
-
status: 401,
|
|
36
|
-
json: async () => {
|
|
37
|
-
throw new Error();
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
const response403 = {
|
|
41
|
-
status: 403,
|
|
42
|
-
json: async () => {
|
|
43
|
-
throw new Error();
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
const response409 = {
|
|
47
|
-
status: 409,
|
|
48
|
-
json: async () => ({
|
|
49
|
-
message: 'An error occurred'
|
|
50
|
-
})
|
|
51
|
-
};
|
|
52
|
-
expect(parseStatus(response401)).rejects.toMatchObject({
|
|
53
|
-
type: 'access',
|
|
54
|
-
message: 'Unauthorized',
|
|
55
|
-
details: {}
|
|
56
|
-
});
|
|
57
|
-
expect(parseStatus(response403)).rejects.toMatchObject({
|
|
58
|
-
type: 'access',
|
|
59
|
-
message: 'Forbidden',
|
|
60
|
-
details: {}
|
|
61
|
-
});
|
|
62
|
-
expect(parseStatus(response409)).rejects.toMatchObject({
|
|
63
|
-
type: 'access',
|
|
64
|
-
message: 'An error occurred',
|
|
65
|
-
details: {
|
|
66
|
-
message: 'An error occurred'
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
it('should throw if an unknown error occurs', () => {
|
|
71
|
-
const response = {
|
|
72
|
-
status: 500,
|
|
73
|
-
statusText: 'Failed',
|
|
74
|
-
json: async () => ({
|
|
75
|
-
message: 'An error occurred'
|
|
76
|
-
})
|
|
77
|
-
};
|
|
78
|
-
expect(parseStatus(response)).rejects.toMatchObject({
|
|
79
|
-
type: 'unknown',
|
|
80
|
-
message: `An unknown error occurred - Failed (500)`,
|
|
81
|
-
details: {
|
|
82
|
-
message: 'An error occurred'
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
describe('fetchData', () => {
|
|
88
|
-
const headers = {
|
|
89
|
-
'Content-Type': type => type === 'json' ? 'application/json' : type === 'text' ? 'text/plain' : 'some/other-content-type'
|
|
90
|
-
};
|
|
91
|
-
const mockFetch = jest.fn(async url => ({
|
|
92
|
-
status: 200,
|
|
93
|
-
headers: {
|
|
94
|
-
get: name => headers[name] && headers[name](url)
|
|
95
|
-
},
|
|
96
|
-
json: async () => ({
|
|
97
|
-
foo: 'bar'
|
|
98
|
-
}),
|
|
99
|
-
text: async () => 'foobar',
|
|
100
|
-
blob: async () => 'blob of foobar'
|
|
101
|
-
}));
|
|
102
|
-
beforeEach(() => {
|
|
103
|
-
jest.clearAllMocks();
|
|
104
|
-
});
|
|
105
|
-
it('Should correctly parse a successful JSON response', () => {
|
|
106
|
-
;
|
|
107
|
-
global.fetch = mockFetch;
|
|
108
|
-
expect(fetchData('json', {})).resolves.toMatchObject({
|
|
109
|
-
foo: 'bar'
|
|
110
|
-
});
|
|
111
|
-
});
|
|
112
|
-
it('Should correctly parse a successful TEXT response', () => {
|
|
113
|
-
;
|
|
114
|
-
global.fetch = mockFetch;
|
|
115
|
-
expect(fetchData('text')).resolves.toBe('foobar');
|
|
116
|
-
});
|
|
117
|
-
it('Should correctly parse a successful BLOB response', () => {
|
|
118
|
-
;
|
|
119
|
-
global.fetch = mockFetch;
|
|
120
|
-
expect(fetchData('something else')).resolves.toBe('blob of foobar');
|
|
121
|
-
});
|
|
122
|
-
it('Should throw a FetchError if fetch fails', () => {
|
|
123
|
-
;
|
|
124
|
-
global.fetch = jest.fn(async () => {
|
|
125
|
-
throw new Error();
|
|
126
|
-
});
|
|
127
|
-
expect(fetchData('failure', {})).rejects.toBeInstanceOf(FetchError);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
});
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* These are metadata resources (from /api/resources) which are known to support paging.
|
|
3
|
-
* They should all also support fields declarations. Only plural resource names are supported.
|
|
4
|
-
* This list may be incomplete, and may require updating for new DHIS2 major versions
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export const normativeMetadataResources = ['programDataElements', 'indicatorTypes', 'programs', 'optionGroups', 'programRuleVariables', 'reports', 'users', 'constants', 'externalMapLayers', 'analyticsTableHooks', 'pushAnalysis', 'oAuth2Clients', 'validationRules', 'reportTables', 'userGroups', 'sqlViews', 'sections', 'validationNotificationTemplates', 'optionGroupSets', 'organisationUnitGroupSets', 'trackedEntityAttributes', 'dashboardItems', 'categoryCombos', 'programSections', 'trackedEntityTypes', 'dataSetNotificationTemplates', 'maps', 'dataApprovalWorkflows', 'programStages', 'categoryOptionGroups', 'relationshipTypes', 'validationRuleGroups', 'predictors', 'dataSets', 'options', 'organisationUnitLevels', 'dataEntryForms', 'predictorGroups', 'dataElementGroupSets', 'programIndicatorGroups', 'dataApprovalLevels', 'organisationUnits', 'programIndicators', 'dataElements', 'mapViews', 'categories', 'categoryOptionCombos', 'documents', 'indicators', 'optionSets', 'interpretations', 'programRuleActions', 'dataElementGroups', 'attributes', 'validationResults', 'categoryOptions', 'indicatorGroupSets', 'messageConversations', 'dashboards', 'programNotificationTemplates', 'programStageSections', 'legendSets', 'organisationUnitGroups', 'visualizations', 'indicatorGroups', 'programTrackedEntityAttributeGroups', 'programRules', 'categoryOptionGroupSets', 'userRoles', 'eventFilters', 'eventReports', 'eventCharts', 'smsCommands', 'jobConfigurations', 'minMaxDataElements', 'charts', 'dataElementOperands',
|
|
8
|
-
// These exist and appear to accept field declarations, but have abnormal behavior when it comes to viewing collections and paging results
|
|
9
|
-
'trackedEntityInstance', 'relationships'];
|
|
10
|
-
|
|
11
|
-
// Including non-normative resources listed under /api/resources for future follow-up
|
|
12
|
-
export const nonNormativeMetadataResources = ['trackedEntityAttributeValues', 'programInstances', 'expressions', 'programStageInstances', 'externalFileResources', 'icons', 'fileResources', 'metadataVersions', 'dataStores',
|
|
13
|
-
// This doesn't exist, but is listed as the plural of 'dataStore' in /api/resources
|
|
14
|
-
|
|
15
|
-
// Known but missing from /api/resources
|
|
16
|
-
'userDataStore', 'apps'];
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export const joinPath = function () {
|
|
2
|
-
for (var _len = arguments.length, parts = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
3
|
-
parts[_key] = arguments[_key];
|
|
4
|
-
}
|
|
5
|
-
const realParts = parts.filter(part => !!part);
|
|
6
|
-
return realParts.map(part => part.replace(/^\/+|\/+$/g, '')).join('/');
|
|
7
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { joinPath } from './path';
|
|
2
|
-
describe('Utils', () => {
|
|
3
|
-
describe('pathJoin', () => {
|
|
4
|
-
it('Should strip all leading and trailing slashes', () => {
|
|
5
|
-
expect(joinPath('///test//')).toBe('test');
|
|
6
|
-
});
|
|
7
|
-
it('Should join path segments with slashes', () => {
|
|
8
|
-
expect(joinPath('a', 'b', 'c', 'd')).toBe('a/b/c/d');
|
|
9
|
-
});
|
|
10
|
-
it('Should only include singular joining slashes', () => {
|
|
11
|
-
expect(joinPath('//a/', 'b//', '///c////', '/d')).toBe('a/b/c/d');
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Requests that expect a "multipart/form-data" Content-Type have been collected by scanning
|
|
3
|
-
* the developer documentation:
|
|
4
|
-
* https://docs.dhis2.org/master/en/developer/html/dhis2_developer_manual_full.html
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Post to 'dataValues' (send/update a data value; endpoint doesn't support JSON)
|
|
8
|
-
// For file-uploads too
|
|
9
|
-
export const isDataValue = (type, _ref) => {
|
|
10
|
-
let {
|
|
11
|
-
resource
|
|
12
|
-
} = _ref;
|
|
13
|
-
return type === 'create' && (resource === 'dataValues' || resource === 'dataValues/file');
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// POST to 'fileResources' (upload a file resource)
|
|
17
|
-
export const isFileResourceUpload = (type, _ref2) => {
|
|
18
|
-
let {
|
|
19
|
-
resource
|
|
20
|
-
} = _ref2;
|
|
21
|
-
return type === 'create' && resource === 'fileResources';
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// POST to 'messageConversations/attachments' (upload a message conversation attachment)
|
|
25
|
-
export const isMessageConversationAttachment = (type, _ref3) => {
|
|
26
|
-
let {
|
|
27
|
-
resource
|
|
28
|
-
} = _ref3;
|
|
29
|
-
return type === 'create' && resource === 'messageConversations/attachments';
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// POST to `staticContent/${key}` (upload staticContent: logo_banner | logo_front)
|
|
33
|
-
export const isStaticContentUpload = (type, _ref4) => {
|
|
34
|
-
let {
|
|
35
|
-
resource
|
|
36
|
-
} = _ref4;
|
|
37
|
-
const pattern = /^staticContent\/(?:logo_banner|logo_front)$/;
|
|
38
|
-
return type === 'create' && pattern.test(resource);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
// POST to 'apps' (install an app)
|
|
42
|
-
export const isAppInstall = (type, _ref5) => {
|
|
43
|
-
let {
|
|
44
|
-
resource
|
|
45
|
-
} = _ref5;
|
|
46
|
-
return type === 'create' && resource === 'apps';
|
|
47
|
-
};
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { isFileResourceUpload, isMessageConversationAttachment, isStaticContentUpload, isAppInstall, isDataValue } from './multipartFormDataMatchers';
|
|
2
|
-
describe('isDataValue', () => {
|
|
3
|
-
it('returns true for a POST to "dataValues"', () => {
|
|
4
|
-
expect(isDataValue('create', {
|
|
5
|
-
resource: 'dataValues'
|
|
6
|
-
})).toBe(true);
|
|
7
|
-
});
|
|
8
|
-
it('returns true for a POST to "dataValues/file"', () => {
|
|
9
|
-
expect(isDataValue('create', {
|
|
10
|
-
resource: 'dataValues/file'
|
|
11
|
-
})).toBe(true);
|
|
12
|
-
});
|
|
13
|
-
it('returns false for a POST to a different resource', () => {
|
|
14
|
-
expect(isDataValue('create', {
|
|
15
|
-
resource: 'somethingElse'
|
|
16
|
-
})).toBe(false);
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
describe('isFileResourceUpload', () => {
|
|
20
|
-
it('returns true for a POST to "fileResources"', () => {
|
|
21
|
-
expect(isFileResourceUpload('create', {
|
|
22
|
-
resource: 'fileResources'
|
|
23
|
-
})).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
it('retuns false for a POST to a different resource', () => {
|
|
26
|
-
expect(isFileResourceUpload('create', {
|
|
27
|
-
resource: 'notFileResources'
|
|
28
|
-
})).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
describe('isMessageConversationAttachment', () => {
|
|
32
|
-
it('returns true for a POST to "messageConversations/attachments"', () => {
|
|
33
|
-
expect(isMessageConversationAttachment('create', {
|
|
34
|
-
resource: 'messageConversations/attachments'
|
|
35
|
-
})).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
it('retuns false for a POST to a different resource', () => {
|
|
38
|
-
expect(isMessageConversationAttachment('create', {
|
|
39
|
-
resource: 'messageConversations/notAttachments'
|
|
40
|
-
})).toBe(false);
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
describe('isStaticContentUpload', () => {
|
|
44
|
-
it('returns true for a POST to "staticContent/logo_banner"', () => {
|
|
45
|
-
expect(isStaticContentUpload('create', {
|
|
46
|
-
resource: 'staticContent/logo_banner'
|
|
47
|
-
})).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
it('returns true for a POST to "staticContent/logo_front"', () => {
|
|
50
|
-
expect(isStaticContentUpload('create', {
|
|
51
|
-
resource: 'staticContent/logo_front'
|
|
52
|
-
})).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
it('returns false for a request to a different resource', () => {
|
|
55
|
-
expect(isStaticContentUpload('create', {
|
|
56
|
-
resource: 'staticContent/no_logo'
|
|
57
|
-
})).toBe(false);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
describe('isAppInstall', () => {
|
|
61
|
-
it('returns true for a POST to "apps"', () => {
|
|
62
|
-
expect(isAppInstall('create', {
|
|
63
|
-
resource: 'apps'
|
|
64
|
-
})).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
it('retuns false for a POST to a different resource', () => {
|
|
67
|
-
expect(isAppInstall('create', {
|
|
68
|
-
resource: 'notApps'
|
|
69
|
-
})).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import * as multipartFormDataMatchers from './multipartFormDataMatchers';
|
|
2
|
-
import * as textPlainMatchers from './textPlainMatchers';
|
|
3
|
-
import * as xWwwFormUrlencodedMatchers from './xWwwFormUrlencodedMatchers';
|
|
4
|
-
const resourceExpectsTextPlain = (type, query) => Object.values(textPlainMatchers).some(textPlainMatcher => textPlainMatcher(type, query));
|
|
5
|
-
const resourceExpectsMultipartFormData = (type, query) => Object.values(multipartFormDataMatchers).some(multipartFormDataMatcher => multipartFormDataMatcher(type, query));
|
|
6
|
-
const resourceExpectsXWwwFormUrlencoded = (type, query) => Object.values(xWwwFormUrlencodedMatchers).some(xWwwFormUrlencodedMatcher => xWwwFormUrlencodedMatcher(type, query));
|
|
7
|
-
const convertData = (data, initialValue) => {
|
|
8
|
-
const dataEntries = Object.entries(data);
|
|
9
|
-
if (dataEntries.length === 0) {
|
|
10
|
-
throw new Error(`Could not convert data to ${initialValue.constructor.name}: object does not have own enumerable string-keyed properties`);
|
|
11
|
-
}
|
|
12
|
-
return dataEntries.reduce((convertedData, _ref) => {
|
|
13
|
-
let [key, value] = _ref;
|
|
14
|
-
convertedData.append(key, value);
|
|
15
|
-
return convertedData;
|
|
16
|
-
}, initialValue);
|
|
17
|
-
};
|
|
18
|
-
export const requestContentType = (type, query) => {
|
|
19
|
-
if (!query.data) {
|
|
20
|
-
return null;
|
|
21
|
-
}
|
|
22
|
-
if (type === 'json-patch') {
|
|
23
|
-
return 'application/json-patch+json';
|
|
24
|
-
}
|
|
25
|
-
if (resourceExpectsTextPlain(type, query)) {
|
|
26
|
-
return 'text/plain';
|
|
27
|
-
}
|
|
28
|
-
if (resourceExpectsMultipartFormData(type, query)) {
|
|
29
|
-
return 'multipart/form-data';
|
|
30
|
-
}
|
|
31
|
-
if (resourceExpectsXWwwFormUrlencoded(type, query)) {
|
|
32
|
-
return 'application/x-www-form-urlencoded';
|
|
33
|
-
}
|
|
34
|
-
return 'application/json';
|
|
35
|
-
};
|
|
36
|
-
export const requestHeadersForContentType = contentType => {
|
|
37
|
-
/*
|
|
38
|
-
* Explicitely setting Content-Type to 'multipart/form-data' produces
|
|
39
|
-
* a "multipart boundary not found" error. By not setting a Content-Type
|
|
40
|
-
* the browser will correctly set it for us and also apply multipart
|
|
41
|
-
* boundaries if the request body is an instance of FormData
|
|
42
|
-
* See https://stackoverflow.com/a/39281156/1143502
|
|
43
|
-
*/
|
|
44
|
-
if (!contentType || contentType === 'multipart/form-data') {
|
|
45
|
-
return undefined;
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
'Content-Type': contentType
|
|
49
|
-
};
|
|
50
|
-
};
|
|
51
|
-
export const requestBodyForContentType = (contentType, _ref2) => {
|
|
52
|
-
let {
|
|
53
|
-
data
|
|
54
|
-
} = _ref2;
|
|
55
|
-
if (typeof data === 'undefined') {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
if (contentType === 'application/json' || contentType === 'application/json-patch+json') {
|
|
59
|
-
return JSON.stringify(data);
|
|
60
|
-
}
|
|
61
|
-
if (contentType === 'multipart/form-data') {
|
|
62
|
-
return convertData(data, new FormData());
|
|
63
|
-
}
|
|
64
|
-
if (contentType === 'application/x-www-form-urlencoded') {
|
|
65
|
-
return convertData(data, new URLSearchParams());
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 'text/plain'
|
|
69
|
-
return data;
|
|
70
|
-
};
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import { requestContentType, requestHeadersForContentType, requestBodyForContentType } from './requestContentType';
|
|
2
|
-
describe('requestContentType', () => {
|
|
3
|
-
it('returns "application/json" for a normal resource', () => {
|
|
4
|
-
expect(requestContentType('create', {
|
|
5
|
-
resource: 'test',
|
|
6
|
-
data: 'test'
|
|
7
|
-
})).toBe('application/json');
|
|
8
|
-
});
|
|
9
|
-
it('returns "application/json-patch+json" when the fetch type is "json-patch"', () => {
|
|
10
|
-
expect(requestContentType('json-patch', {
|
|
11
|
-
resource: 'test',
|
|
12
|
-
data: 'test'
|
|
13
|
-
})).toBe('application/json-patch+json');
|
|
14
|
-
});
|
|
15
|
-
it('returns "multipart/form-data" for a specific resource that expects it', () => {
|
|
16
|
-
expect(requestContentType('create', {
|
|
17
|
-
resource: 'fileResources',
|
|
18
|
-
data: 'test'
|
|
19
|
-
})).toBe('multipart/form-data');
|
|
20
|
-
});
|
|
21
|
-
it('returns "text/plain" for a specific resource that expects it', () => {
|
|
22
|
-
expect(requestContentType('create', {
|
|
23
|
-
resource: 'messageConversations/feedback',
|
|
24
|
-
data: 'test'
|
|
25
|
-
})).toBe('text/plain');
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
describe('requestHeadersForContentType', () => {
|
|
29
|
-
it('returns undefined if contentType is null', () => {
|
|
30
|
-
expect(requestHeadersForContentType(null)).toBe(undefined);
|
|
31
|
-
});
|
|
32
|
-
it('returns undefined if contentType is "multipart/form-data"', () => {
|
|
33
|
-
expect(requestHeadersForContentType('multipart/form-data')).toBe(undefined);
|
|
34
|
-
});
|
|
35
|
-
it('returns a headers object with the contentType for "application/json"', () => {
|
|
36
|
-
expect(requestHeadersForContentType('application/json')).toEqual({
|
|
37
|
-
'Content-Type': 'application/json'
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
it('returns a headers object with the contentType for "text/plain"', () => {
|
|
41
|
-
expect(requestHeadersForContentType('text/plain')).toEqual({
|
|
42
|
-
'Content-Type': 'text/plain'
|
|
43
|
-
});
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
describe('requestBodyForContentType', () => {
|
|
47
|
-
it('returns undefined if data is undefined', () => {
|
|
48
|
-
expect(requestBodyForContentType('application/json', {
|
|
49
|
-
resource: 'test'
|
|
50
|
-
})).toBe(undefined);
|
|
51
|
-
});
|
|
52
|
-
it('JSON stringifies the data if contentType is "application/json"', () => {
|
|
53
|
-
const dataIn = {
|
|
54
|
-
a: 'AAAA',
|
|
55
|
-
b: 1,
|
|
56
|
-
c: true
|
|
57
|
-
};
|
|
58
|
-
const dataOut = JSON.stringify(dataIn);
|
|
59
|
-
expect(requestBodyForContentType('application/json', {
|
|
60
|
-
resource: 'test',
|
|
61
|
-
data: dataIn
|
|
62
|
-
})).toBe(dataOut);
|
|
63
|
-
});
|
|
64
|
-
it('converts to FormData if contentType is "multipart/form-data"', () => {
|
|
65
|
-
const file = new File(['foo'], 'foo.txt', {
|
|
66
|
-
type: 'text/plain'
|
|
67
|
-
});
|
|
68
|
-
const data = {
|
|
69
|
-
a: 'AAA',
|
|
70
|
-
file
|
|
71
|
-
};
|
|
72
|
-
const result = requestBodyForContentType('multipart/form-data', {
|
|
73
|
-
resource: 'test',
|
|
74
|
-
data
|
|
75
|
-
});
|
|
76
|
-
expect(result instanceof FormData).toBe(true);
|
|
77
|
-
expect(result.get('a')).toBe('AAA');
|
|
78
|
-
expect(result.get('file')).toBe(file);
|
|
79
|
-
});
|
|
80
|
-
it('throws an error if contentType is "multipart/form-data" and data does have own string-keyd properties', () => {
|
|
81
|
-
expect(() => {
|
|
82
|
-
requestBodyForContentType('multipart/form-data', {
|
|
83
|
-
resource: 'test',
|
|
84
|
-
data: new File(['foo'], 'foo.txt', {
|
|
85
|
-
type: 'text/plain'
|
|
86
|
-
})
|
|
87
|
-
});
|
|
88
|
-
}).toThrowErrorMatchingInlineSnapshot(`"Could not convert data to FormData: object does not have own enumerable string-keyed properties"`);
|
|
89
|
-
});
|
|
90
|
-
it('converts to URLSearchParams if contentType is "application/x-www-form-urlencoded"', () => {
|
|
91
|
-
const data = {
|
|
92
|
-
a: 'AAA'
|
|
93
|
-
};
|
|
94
|
-
const result = requestBodyForContentType('application/x-www-form-urlencoded', {
|
|
95
|
-
resource: 'test',
|
|
96
|
-
data
|
|
97
|
-
});
|
|
98
|
-
expect(result instanceof URLSearchParams).toBe(true);
|
|
99
|
-
expect(result.get('a')).toBe('AAA');
|
|
100
|
-
});
|
|
101
|
-
it('throws an error if contentType is "application/x-www-form-urlencoded" and data does have own string-keyd properties', () => {
|
|
102
|
-
expect(() => {
|
|
103
|
-
requestBodyForContentType('application/x-www-form-urlencoded', {
|
|
104
|
-
resource: 'test',
|
|
105
|
-
data: new File(['foo'], 'foo.txt', {
|
|
106
|
-
type: 'text/plain'
|
|
107
|
-
})
|
|
108
|
-
});
|
|
109
|
-
}).toThrowErrorMatchingInlineSnapshot(`"Could not convert data to URLSearchParams: object does not have own enumerable string-keyed properties"`);
|
|
110
|
-
});
|
|
111
|
-
it('returns the data as received if contentType is "text/plain"', () => {
|
|
112
|
-
const data = 'Something';
|
|
113
|
-
expect(requestBodyForContentType('text/plain', {
|
|
114
|
-
resource: 'messageConversations/feedback',
|
|
115
|
-
data
|
|
116
|
-
})).toBe(data);
|
|
117
|
-
});
|
|
118
|
-
});
|