forked from hummingbot/gateway
-
Notifications
You must be signed in to change notification settings - Fork 2
/
patch.ts
106 lines (91 loc) · 3.18 KB
/
patch.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
import path from 'path';
import fs from 'fs';
import os from 'os';
let patchedObjects: Set<any> = new Set();
export const classHasGetter = (obj: any, prop: string): boolean => {
const description = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(obj),
prop
);
if (description) {
return !!description.get;
}
return false;
};
// override an existing property value, but make the old one recoverable.
export const patch = (target: any, propertyName: string, mock: any): void => {
// clean up a target if it has already been patched, this avoids issues in unpatch
if (patchedObjects.has(target)) patchedObjects.delete(target);
// only store the previous property if it has not been mocked yet, this way we preserve
// the original non mocked value
if (!('__original__' + propertyName in target)) {
if (Object.getOwnPropertyDescriptor(target, propertyName)) {
// general case
target['__original__' + propertyName] = target[propertyName];
} else {
// special case for getters and setters
target['__original__' + propertyName] = Object.getOwnPropertyDescriptor(
Object.getPrototypeOf(target),
propertyName
);
}
}
if (classHasGetter(target, propertyName)) {
// special case for getter without setter
const targetPrototype = Object.getPrototypeOf(target);
Object.defineProperty(targetPrototype, propertyName, {
get: mock,
// this is a dummy setter, there needs to be a setter in order to change the getter
// the idea is that the mock overrides the getter and ignores the setter
set: (_value: any) => {
return;
},
});
Object.setPrototypeOf(target, targetPrototype);
} else {
// general case
target[propertyName] = mock;
}
patchedObjects.add(target);
};
// recover all old property values from before the patch.
export const unpatch = (): void => {
patchedObjects.forEach((target: any) => {
const keys = Object.keys(target);
keys.forEach((key: string) => {
if (key.startsWith('__original__')) {
const propertyName = key.slice(12);
if (Object.getOwnPropertyDescriptor(target, propertyName)) {
// the property exists directly on the object
target[propertyName] = target[key];
} else {
// the property is at a lower level in the object, it is likely a getter or setter
const targetPrototype = Object.getPrototypeOf(target);
Object.defineProperty(targetPrototype, propertyName, target[key]);
Object.setPrototypeOf(target, targetPrototype);
}
delete target[key];
}
});
});
patchedObjects = new Set();
};
let currDir: string | null = null;
let tempDir: string;
export function setUpTempDir(dirPrefix: string = ''): string {
if (currDir !== null) {
throw new Error('Temp dir already set up');
}
currDir = process.cwd();
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), dirPrefix));
process.chdir(tempDir);
return tempDir;
}
export function tearDownTempDir(): void {
if (currDir === null) {
throw new Error('Temp dir not set up');
}
process.chdir(currDir as string);
fs.rmSync(tempDir, { recursive: true });
currDir = null;
}