-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathtest-destination-provider.ts
259 lines (225 loc) · 8.06 KB
/
test-destination-provider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
/* Copyright (c) 2020 SAP SE or an SAP affiliate company. All rights reserved. */
import { existsSync, readdirSync, readFileSync } from 'fs';
import { join, parse, resolve, sep } from 'path';
import { Destination } from '@sap-cloud-sdk/core';
import { createLogger } from '@sap-cloud-sdk/util';
const logger = createLogger({
package: 'test-util',
messageContext: 'test-destination-provider'
});
const SYSTEMS_FILE = 'systems.json';
const CREDENTIALS_FILE = 'credentials.json';
export interface GetTestDestinationOptions {
systemsFilePath?: string;
credentialsFilePath?: string;
}
interface System {
alias: string;
uri: string;
sapClient?: string;
}
interface SystemsFile {
systems: System[];
}
interface Credentials {
alias: string;
username: string;
password: string;
}
interface CredentialsFile {
credentials: Credentials[];
}
const formatSystemJson = `Format of systems.json is:
{
systems:[
{
alias: A unique identifier. Used for matching a system and credentials.
uri: A unique resource identifier like "http://mySystem.com"
sapClient?: The sap client as string e.g. "001"
},...
]
}`;
const formatCredentials = `Format of credentials.json is:
{
credentials:[
{
alias: A unique identifier. Used for matching a system and credentials.
username: The username used for basic authentication.
password: The password used for basic autentication.
},...
]
}`;
/**
* Loads a destination matching the provided alias stored in `systems.json` and `credentials.json`.
* By default, this function starts looking in the directory the test process has been started in (i.e. '.')
* and traverses the file hierarchy upwards until it finds a systems.json and credentials.json file.
* Alternatively, you can supply paths to the systems and the credentials file directly.
*
* Throws an error when no systems.json can be found, the alias does not match any of the available destinations,
* the JSON is malformed or one of the supplied paths is invalid.
* Does not throw an error when no credentials.json can be found, but will print a warning.
*
* @param alias - The alias identifying the destination
* @param options - References to the `systems.json` and `credentials.json` files
* @returns An array of destinations
*/
export function getTestDestinationByAlias(
alias: string,
options?: GetTestDestinationOptions
): Destination {
const destinations = getTestDestinations(options);
const matchingDestination = destinations.find(d => d.name === alias);
if (!matchingDestination) {
throw new Error(
`Couldn't find destination that matches the provided name "${alias}".
The following destinations could be found: ${destinations
.map(d => d.name)
.join(', ')}`
);
}
return matchingDestination;
}
/**
* Loads all destinations stored in `systems.json` and `credentials.json` files.
*
* By default, this functions starts looking in the directory the test process has been started in (i.e. '.')
* and traverses the file hierarchy upwards until it finds a systems.json and credentials.json file.
* Alternatively, you can supply paths to the systems and the credentials file directly.
*
* Throws an error when no systems.json can be found, the JSON is malformed or one of the supplied paths is invalid.
* Does not throw an error when no credentials.json can be found, but will print a warning.
*
* @param options - References to the `systems.json` and `credentials.json` files
* @returns An array of destinations
*/
export function getTestDestinations(
options?: GetTestDestinationOptions
): Destination[] {
const systems = readSystemsOrFail(options);
const credentials = readCredentialsOrFail(options);
return toDestinations(mergeSystemsAndCredentials(systems, credentials));
}
function readSystemsOrFail(options?: GetTestDestinationOptions): System[] {
if (options && options.systemsFilePath) {
if (!existsSync(options.systemsFilePath)) {
throw new Error(
`The provided path (${options.systemsFilePath}) to the systems file is invalid!`
);
}
return readSystems(options.systemsFilePath).systems;
}
const foundPath = findFileSearchingUpwards(process.cwd(), SYSTEMS_FILE);
if (!foundPath) {
throw new Error(
`No ${SYSTEMS_FILE} could be found when searching in directory ${process.cwd()} and upwards and no paths have been provided directly. ${formatSystemJson}`
);
}
return readSystems(join(foundPath, SYSTEMS_FILE)).systems;
}
function readCredentialsOrFail(
options?: GetTestDestinationOptions
): Credentials[] {
if (options && options.credentialsFilePath) {
if (existsSync(options.credentialsFilePath)) {
return readCredentials(options.credentialsFilePath).credentials;
} else {
throw new Error(
`The provided path (${options.credentialsFilePath}) to the credentials file is invalid!`
);
}
}
const foundPath = findFileSearchingUpwards(process.cwd(), CREDENTIALS_FILE);
if (foundPath && existsSync(join(foundPath, CREDENTIALS_FILE))) {
return readCredentials(join(foundPath, CREDENTIALS_FILE)).credentials;
}
logger.warn(
`No path to a ${CREDENTIALS_FILE} provided and none found next to ${foundPath}${sep}${SYSTEMS_FILE}. Proceeding without credentials.`
);
return [];
}
function mergeSystemsAndCredentials(systems, credentials) {
return systems.map(system => ({
...system,
...credentials.find(cred => cred.alias === system.alias)
}));
}
function toDestinations(systemsAndCredentials): Destination[] {
return systemsAndCredentials.map(sysAndCred => ({
name: sysAndCred.alias,
url: sysAndCred.uri,
username: sysAndCred.username,
password: sysAndCred.password,
sapClient: sysAndCred.sapClient,
isTestDestination: true
}));
}
function findFileSearchingUpwards(
dir: string,
fileName: string
): string | null {
const files = readdirSync(dir);
// TODO: use util method to find proper project root instead of the overall root
const rootPath = parse(process.cwd()).root;
if (files.includes(fileName)) {
return dir;
}
if (dir === rootPath) {
return null;
}
const oneDirUp = resolve(dir, '..');
return findFileSearchingUpwards(oneDirUp, fileName);
}
function readSystems(filePath: string): SystemsFile {
const systemfile = readJson(filePath) as SystemsFile;
if (!systemfile.systems || systemfile.systems.length === 0) {
throw new Error(`No systems provided in ${filePath}.
If you do not want to define systems just remove the file. ${formatSystemJson}`);
}
systemfile.systems.forEach(system => {
if (!system.alias || !system.uri) {
throw new Error(`A system in ${filePath} is not valid - Mandatory alias or url missing.
Broken entry is: ${JSON.stringify(
system
)}. ${formatSystemJson}`);
}
});
return systemfile;
}
function readCredentials(filePath: string): CredentialsFile {
const credentialsFile = readJson(filePath) as CredentialsFile;
if (
!credentialsFile.credentials ||
credentialsFile.credentials.length === 0
) {
throw new Error(`No credentials provided in ${filePath}.
If you do not want to define credentials just remove the file. ${formatCredentials}`);
}
credentialsFile.credentials.forEach(cred => {
if (!cred.alias || !cred.username || !cred.password) {
throw new Error(`A credential in ${filePath} is not valid - Mandatory alias, username or password missing.
Broken entry is: ${JSON.stringify(
cred
)}. ${formatCredentials}`);
}
});
return credentialsFile;
}
function readJson(filePath: string): { [key: string]: any } {
let content;
try {
content = readFileSync(filePath, 'utf8');
} catch (error) {
throw new Error(
`Failed to read file at path: ${filePath}.
Original error: ${error.message}`
);
}
try {
return JSON.parse(content);
} catch (error) {
throw new Error(
`File read from path ${filePath} is not valid JSON.
Original error: ${error.message}`
);
}
}