• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AleksandrRogov / DynamicsWebApi / 28292572768

27 Jun 2026 02:51PM UTC coverage: 99.273% (+0.5%) from 98.767%
28292572768

push

github

AleksandrRogov
chore: deps upgrade and migrating from nyc to c8 for coverage report

1002 of 1026 branches covered (97.66%)

Branch coverage included in aggregate %.

5959 of 5986 relevant lines covered (99.55%)

39.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.99
/src/client/RequestClient.ts
1
import type * as Core from "../types.js";
1✔
2
import type { AccessToken } from "../dynamics-web-api.js";
1✔
3
import type { InternalConfig } from "../utils/Config.js";
1✔
4
import { generateUUID, isRunningWithinPortals, isNull } from "../utils/Utility.js";
1✔
5
import * as EntityMapper from "./helpers/entityNameMapper.js";
1✔
6
import { executeRequest } from "./helpers/executeRequest.js";
1✔
7
import { DynamicsWebApiError, ErrorHelper } from "../helpers/ErrorHelper.js";
1✔
8
import { composeRequest, convertToBatch, processData, setStandardHeaders } from "./request/index.js";
1✔
9

1✔
10
const _addResponseParams = (requestId: string, responseParams: Record<string, any>) => {
1✔
11
    if (_responseParseParams[requestId]) _responseParseParams[requestId].push(responseParams);
334✔
12
    else _responseParseParams[requestId] = [responseParams];
285✔
13
};
334✔
14

1✔
15
const _addRequestToBatchCollection = (requestId: string, request: Core.InternalRequest) => {
1✔
16
    if (_batchRequestCollection[requestId]) _batchRequestCollection[requestId].push(request);
49✔
17
    else _batchRequestCollection[requestId] = [request];
21✔
18
};
49✔
19

1✔
20
const _clearRequestData = (requestId: string): void => {
1✔
21
    delete _responseParseParams[requestId];
560✔
22
    if (_batchRequestCollection.hasOwnProperty(requestId)) delete _batchRequestCollection[requestId];
560!
23
};
560✔
24

1✔
25
const _runRequest = async (request: Core.InternalRequest, config: InternalConfig): Promise<Core.WebApiResponse> => {
1✔
26
    try {
280✔
27
        const result = await sendRequest(request, config);
280✔
28
        _clearRequestData(request.requestId!);
255✔
29

255✔
30
        return result;
255✔
31
    } catch (error) {
280✔
32
        _clearRequestData(request.requestId!);
25✔
33
        throw error;
25✔
34
    } finally {
280!
35
        _clearRequestData(request.requestId!);
280✔
36
    }
280✔
37
};
280✔
38

1✔
39
let _batchRequestCollection: Core.BatchRequestCollection = {};
1✔
40
let _responseParseParams: { [key: string]: any[] } = {};
1✔
41

1✔
42
const _nameExceptions = [
1✔
43
    "$metadata",
1✔
44
    "EntityDefinitions",
1✔
45
    "RelationshipDefinitions",
1✔
46
    "GlobalOptionSetDefinitions",
1✔
47
    "ManagedPropertyDefinitions",
1✔
48
    "query",
1✔
49
    "suggest",
1✔
50
    "autocomplete",
1✔
51
];
1✔
52

1✔
53
const _isEntityNameException = (entityName: string): boolean => {
1✔
54
    return _nameExceptions.indexOf(entityName) > -1;
262✔
55
};
262✔
56

1✔
57
const _getCollectionNames = async (entityName: string, config: InternalConfig): Promise<string | null | undefined> => {
1✔
58
    if (!isNull(EntityMapper.entityNames)) {
6✔
59
        return EntityMapper.findCollectionName(entityName) || entityName;
1!
60
    }
1✔
61

5✔
62
    const request = composeRequest(
5✔
63
        {
5✔
64
            method: "GET",
5✔
65
            collection: "EntityDefinitions",
5✔
66
            select: ["EntitySetName", "LogicalName"],
5✔
67
            noCache: true,
5✔
68
            functionName: "retrieveMultiple",
5✔
69
        },
5✔
70
        config,
5✔
71
    );
5✔
72

5✔
73
    const result = await _runRequest(request, config);
5✔
74
    EntityMapper.setEntityNames({});
5✔
75
    for (let i = 0; i < result.data.value.length; i++) {
6✔
76
        EntityMapper.entityNames![result.data.value[i].LogicalName] = result.data.value[i].EntitySetName;
10✔
77
    }
10✔
78

5✔
79
    return EntityMapper.findCollectionName(entityName) || entityName;
6✔
80
};
6✔
81

1✔
82
const _checkCollectionName = async (entityName: string | null | undefined, config: InternalConfig): Promise<string | null | undefined> => {
1✔
83
    if (!entityName || _isEntityNameException(entityName)) {
275✔
84
        return entityName;
72✔
85
    }
72✔
86

203✔
87
    entityName = entityName.toLowerCase();
203✔
88

203✔
89
    if (!config.useEntityNames) {
275✔
90
        return entityName;
197✔
91
    }
197✔
92

6✔
93
    try {
6✔
94
        return await _getCollectionNames(entityName, config);
6✔
95
    } catch (error: any) {
275!
96
        throw new Error("Unable to fetch Collection Names. Error: " + (error as DynamicsWebApiError).message);
×
97
    }
×
98
};
275✔
99

1✔
100
/**
1✔
101
 * Sends a request to given URL with given parameters
1✔
102
 *
1✔
103
 * @param {InternalRequest} request - Composed request to D365 Web Api
1✔
104
 * @param {InternalConfig} config - DynamicsWebApi config.
1✔
105
 */
1✔
106
export const sendRequest = async (request: Core.InternalRequest, config: InternalConfig): Promise<Core.WebApiResponse> => {
1✔
107
    request.headers = request.headers || {};
285✔
108
    request.responseParameters = request.responseParameters || {};
285✔
109
    request.requestId = request.requestId || generateUUID();
285✔
110

285✔
111
    //add response parameters to parse
285✔
112
    _addResponseParams(request.requestId, request.responseParameters);
285✔
113

285✔
114
    //stringify passed data
285✔
115
    let processedData = null;
285✔
116

285✔
117
    const isBatchConverted = request.responseParameters?.convertedToBatch;
285✔
118

285✔
119
    if (request.path === "$batch" && !isBatchConverted) {
285✔
120
        const batchRequest = _batchRequestCollection[request.requestId];
22✔
121

22✔
122
        if (!batchRequest) throw ErrorHelper.batchIsEmpty();
22✔
123

21✔
124
        const batchResult = convertToBatch(batchRequest, config, request);
21✔
125

21✔
126
        processedData = batchResult.body;
21✔
127
        request.headers = { ...batchResult.headers, ...request.headers };
21✔
128

21✔
129
        //clear an array of requests
21✔
130
        delete _batchRequestCollection[request.requestId];
21✔
131
    } else {
285✔
132
        processedData = !isBatchConverted ? processData(request.data, config) : request.data;
263✔
133

263✔
134
        // don't set headers if the request is a part of batch request
263✔
135
        // or if it is set to not include default dataverse headers
263✔
136
        // todo: use the latter option in batch requests as well
263✔
137
        if (!isBatchConverted && request.includeDefaultDataverseHeaders !== false) {
263✔
138
            request.headers = setStandardHeaders(request.headers, request.data);
259✔
139
        }
259✔
140
    }
263✔
141

284✔
142
    if (config.impersonate && !request.headers!["MSCRMCallerID"]) {
285✔
143
        request.headers!["MSCRMCallerID"] = config.impersonate;
6✔
144
    }
6✔
145

284✔
146
    if (config.impersonateAAD && !request.headers!["CallerObjectId"]) {
285✔
147
        request.headers!["CallerObjectId"] = config.impersonateAAD;
1✔
148
    }
1✔
149

284✔
150
    let token: AccessToken | string | null = null;
284✔
151

284✔
152
    //call a token refresh callback only if it is set and there is no "Authorization" header set yet
284✔
153
    if (config.onTokenRefresh && (!request.headers || (request.headers && !request.headers["Authorization"]))) {
285✔
154
        token = await config.onTokenRefresh();
6✔
155
        if (!token) throw new Error("Token is empty. Request is aborted.");
6✔
156
    }
6✔
157

283✔
158
    if (token) {
285✔
159
        request.headers!["Authorization"] = "Bearer " + (token.hasOwnProperty("accessToken") ? (token as AccessToken).accessToken : token);
5✔
160
    }
5✔
161

283✔
162
    if (isRunningWithinPortals()) {
285✔
163
        request.headers!["__RequestVerificationToken"] = await global.window.shell!.getTokenDeferred();
1✔
164
    }
1✔
165

283✔
166
    const url = request.apiConfig ? request.apiConfig.url : config.dataApi.url;
285✔
167

285✔
168
    return await executeRequest({
285✔
169
        method: request.method!,
285✔
170
        uri: url!.toString() + request.path,
285✔
171
        data: processedData,
285✔
172
        proxy: config.proxy,
285✔
173
        isAsync: request.async,
285✔
174
        headers: request.headers!,
285✔
175
        requestId: request.requestId!,
285✔
176
        abortSignal: request.signal,
285✔
177
        responseParams: _responseParseParams,
285✔
178
        timeout: request.timeout || config.timeout,
285✔
179
    });
285✔
180
};
285✔
181

1✔
182
export const makeRequest = async (request: Core.InternalRequest, config: InternalConfig): Promise<Core.WebApiResponse | undefined> => {
1✔
183
    request.responseParameters = request.responseParameters || {};
324✔
184
    //we don't want to mix headers set by the library and by the user
324✔
185
    request.userHeaders = request.headers;
324✔
186
    delete request.headers;
324✔
187

324✔
188
    if (!request.isBatch) {
324✔
189
        const collectionName = await _checkCollectionName(request.collection, config);
275✔
190

275✔
191
        request.collection = collectionName;
275✔
192
        composeRequest(request, config);
275✔
193
        request.responseParameters.convertedToBatch = false;
275✔
194

275✔
195
        //the URL contains more characters than max possible limit, convert the request to a batch request
275✔
196
        if (request.path!.length > 2000) {
275✔
197
            const batchRequest = convertToBatch([request], config);
2✔
198

2✔
199
            //#175 authorization header must be copied as well.
2✔
200
            //todo: is it the only one that needs to be copied?
2✔
201
            if (request.headers!["Authorization"]) {
2✔
202
                batchRequest.headers["Authorization"] = request.headers!["Authorization"];
1✔
203
            }
1✔
204

2✔
205
            request.method = "POST";
2✔
206
            request.path = "$batch";
2✔
207
            request.data = batchRequest.body;
2✔
208
            request.headers = { ...batchRequest.headers, ...request.userHeaders };
2✔
209
            request.responseParameters.convertedToBatch = true;
2✔
210
        }
2✔
211

275✔
212
        return _runRequest(request, config);
275✔
213
    }
275✔
214

49✔
215
    //no need to make a request to web api if it's a part of batch
49✔
216
    composeRequest(request, config);
49✔
217
    //add response parameters to parse
49✔
218
    _addResponseParams(request.requestId!, request.responseParameters);
49✔
219
    _addRequestToBatchCollection(request.requestId!, request);
49✔
220
};
49✔
221

1✔
222
export const _clearTestData = (): void => {
1✔
223
    EntityMapper.setEntityNames(null);
15✔
224
    _responseParseParams = {};
15✔
225
    _batchRequestCollection = {};
15✔
226
};
15✔
227

1✔
228
export const getCollectionName = (entityName: string): string | null => {
1✔
229
    return EntityMapper.findCollectionName(entityName);
1✔
230
};
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc