@kwiz/node 1.0.15 → 1.0.17
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/.github/workflows/npm-publish.yml +24 -0
- package/.madgerc +2 -2
- package/LICENSE +21 -21
- package/README.md +2 -2
- package/dist/exports-index.d.ts +1 -0
- package/dist/exports-index.js +1 -0
- package/dist/exports-index.js.map +1 -1
- package/dist/get-with-cache.d.ts +12 -0
- package/dist/get-with-cache.js +43 -0
- package/dist/get-with-cache.js.map +1 -0
- package/fix-folder-imports.js +26 -26
- package/package.json +78 -78
- package/src/SPO/common.ts +16 -16
- package/src/auth/discovery.test.js +8 -8
- package/src/auth/discovery.ts +60 -60
- package/src/auth/msal.ts +43 -43
- package/src/axios.ts +44 -44
- package/src/exports-index.ts +1 -0
- package/src/get-with-cache.ts +38 -0
- package/src/graph/graph.ts +17 -17
- package/src/index.ts +1 -1
- package/src/storage/common.ts +15 -15
- package/src/storage/odata.ts +86 -86
- package/src/storage/table-storage.test.js +134 -134
- package/src/storage/table-storage.ts +313 -313
- package/__azurite_db_blob__.json +0 -1
- package/__azurite_db_blob_extent__.json +0 -1
- package/__azurite_db_queue__.json +0 -1
- package/__azurite_db_queue_extent__.json +0 -1
- package/__azurite_db_table__.json +0 -1
- package/dist/SPO/index.d.ts +0 -1
- package/dist/SPO/index.js +0 -18
- package/dist/SPO/index.js.map +0 -1
- package/dist/auth/index.d.ts +0 -2
- package/dist/auth/index.js +0 -19
- package/dist/auth/index.js.map +0 -1
package/src/axios.ts
CHANGED
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import { isNullOrEmptyString } from "@kwiz/common";
|
|
2
|
-
import { AxiosError, AxiosRequestConfig } from "axios";
|
|
3
|
-
import { Agent, globalAgent } from "https";
|
|
4
|
-
|
|
5
|
-
type axiosConfigOptions = {
|
|
6
|
-
contantType?: "application/json" | "application/json; odata=nometadata" | "application/xml";
|
|
7
|
-
}
|
|
8
|
-
export function getAxiosConfigBearer(token: string, options?: axiosConfigOptions) {
|
|
9
|
-
return getAxiosConfig(`Bearer ${token}`, options);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function getAxiosConfig(token?: string, options?: axiosConfigOptions) {
|
|
13
|
-
//allow self sign ssl certificates
|
|
14
|
-
globalAgent.options.rejectUnauthorized = false;
|
|
15
|
-
const config: AxiosRequestConfig<any> = {
|
|
16
|
-
httpAgent: new Agent({
|
|
17
|
-
rejectUnauthorized: false
|
|
18
|
-
}),
|
|
19
|
-
headers: {}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
if (!isNullOrEmptyString(token))
|
|
23
|
-
config.headers!.Authorization = token;
|
|
24
|
-
|
|
25
|
-
if (options) {
|
|
26
|
-
if (!isNullOrEmptyString(options.contantType)) {
|
|
27
|
-
config.headers!["Content-Type"] = options.contantType;
|
|
28
|
-
config.headers!["Accept"] = options.contantType;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
return config;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getAxiosErrorData(error: AxiosError) {
|
|
35
|
-
let code = error.code || "Unknown";
|
|
36
|
-
let errorMessage = error.message || "Unspecified error";
|
|
37
|
-
if (error && error.response && error.response.data && error.response.data["odata.error"]) {
|
|
38
|
-
let errorData: { code: string; message: { value: string; }; } = error.response.data["odata.error"];
|
|
39
|
-
if (errorData.message && errorData.message.value)
|
|
40
|
-
errorMessage = errorData.message.value;
|
|
41
|
-
if (errorData && errorData.code)
|
|
42
|
-
code = errorData.code;
|
|
43
|
-
}
|
|
44
|
-
return { code: code, message: errorMessage };
|
|
1
|
+
import { isNullOrEmptyString } from "@kwiz/common";
|
|
2
|
+
import { AxiosError, AxiosRequestConfig } from "axios";
|
|
3
|
+
import { Agent, globalAgent } from "https";
|
|
4
|
+
|
|
5
|
+
type axiosConfigOptions = {
|
|
6
|
+
contantType?: "application/json" | "application/json; odata=nometadata" | "application/xml";
|
|
7
|
+
}
|
|
8
|
+
export function getAxiosConfigBearer(token: string, options?: axiosConfigOptions) {
|
|
9
|
+
return getAxiosConfig(`Bearer ${token}`, options);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getAxiosConfig(token?: string, options?: axiosConfigOptions) {
|
|
13
|
+
//allow self sign ssl certificates
|
|
14
|
+
globalAgent.options.rejectUnauthorized = false;
|
|
15
|
+
const config: AxiosRequestConfig<any> = {
|
|
16
|
+
httpAgent: new Agent({
|
|
17
|
+
rejectUnauthorized: false
|
|
18
|
+
}),
|
|
19
|
+
headers: {}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
if (!isNullOrEmptyString(token))
|
|
23
|
+
config.headers!.Authorization = token;
|
|
24
|
+
|
|
25
|
+
if (options) {
|
|
26
|
+
if (!isNullOrEmptyString(options.contantType)) {
|
|
27
|
+
config.headers!["Content-Type"] = options.contantType;
|
|
28
|
+
config.headers!["Accept"] = options.contantType;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return config;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getAxiosErrorData(error: AxiosError) {
|
|
35
|
+
let code = error.code || "Unknown";
|
|
36
|
+
let errorMessage = error.message || "Unspecified error";
|
|
37
|
+
if (error && error.response && error.response.data && error.response.data["odata.error"]) {
|
|
38
|
+
let errorData: { code: string; message: { value: string; }; } = error.response.data["odata.error"];
|
|
39
|
+
if (errorData.message && errorData.message.value)
|
|
40
|
+
errorMessage = errorData.message.value;
|
|
41
|
+
if (errorData && errorData.code)
|
|
42
|
+
code = errorData.code;
|
|
43
|
+
}
|
|
44
|
+
return { code: code, message: errorMessage };
|
|
45
45
|
}
|
package/src/exports-index.ts
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { IDictionary, isNullOrUndefined } from "@kwiz/common";
|
|
2
|
+
|
|
3
|
+
const $$cache: IDictionary<{ expires: Date; value: any }> = {};
|
|
4
|
+
|
|
5
|
+
export async function getWithCache<T>(worker: () => Promise<{ success: boolean; value: T }>, info: {
|
|
6
|
+
/** seconds */
|
|
7
|
+
successCacheDuration: number;
|
|
8
|
+
/** seconds */
|
|
9
|
+
failedCacheDuration?: number;
|
|
10
|
+
/** must be unique for your call! function name, and parameters */
|
|
11
|
+
cacheKey: string;
|
|
12
|
+
forceRefresh?: boolean;
|
|
13
|
+
}) {
|
|
14
|
+
const now = new Date();
|
|
15
|
+
//purge old values
|
|
16
|
+
Object.keys($$cache).forEach(key => {
|
|
17
|
+
if ($$cache[key].expires < now) delete $$cache[key];
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
let cached = info.forceRefresh ? null : $$cache[info.cacheKey];
|
|
21
|
+
|
|
22
|
+
if (isNullOrUndefined(cached)) {
|
|
23
|
+
const result = await worker();
|
|
24
|
+
if (result.success) {
|
|
25
|
+
$$cache[info.cacheKey] = {
|
|
26
|
+
expires: new Date(new Date().getTime() + info.successCacheDuration * 1000),
|
|
27
|
+
value: result.value
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
else if (info.failedCacheDuration > 0) {
|
|
31
|
+
$$cache[info.cacheKey] = {
|
|
32
|
+
expires: new Date(new Date().getTime() + info.failedCacheDuration * 1000),
|
|
33
|
+
value: result.value
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return $$cache[info.cacheKey].value;
|
|
38
|
+
}
|
package/src/graph/graph.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { AuthContextType, ITenantInfo, isNullOrUndefined } from "@kwiz/common";
|
|
2
|
-
import { GetMSALToken } from "../auth/msal";
|
|
3
|
-
import { getAxiosConfigBearer } from "../axios";
|
|
4
|
-
|
|
5
|
-
/** "https://graph.microsoft.com" */
|
|
6
|
-
export const graphScope = "https://graph.microsoft.com";
|
|
7
|
-
|
|
8
|
-
var auth: AuthContextType = null;
|
|
9
|
-
export function ConfigureGraphAuth(config?: AuthContextType) {
|
|
10
|
-
auth = config;
|
|
11
|
-
}
|
|
12
|
-
export async function getAxiosConfigGraph(tenantInfo: ITenantInfo, clearCache?: boolean) {
|
|
13
|
-
if (isNullOrUndefined(auth)) throw Error("Call ConfigureGraphAuth first");
|
|
14
|
-
|
|
15
|
-
// secret or certificate supported
|
|
16
|
-
let token = await GetMSALToken(tenantInfo, graphScope, auth, clearCache);
|
|
17
|
-
return getAxiosConfigBearer(token);
|
|
1
|
+
import { AuthContextType, ITenantInfo, isNullOrUndefined } from "@kwiz/common";
|
|
2
|
+
import { GetMSALToken } from "../auth/msal";
|
|
3
|
+
import { getAxiosConfigBearer } from "../axios";
|
|
4
|
+
|
|
5
|
+
/** "https://graph.microsoft.com" */
|
|
6
|
+
export const graphScope = "https://graph.microsoft.com";
|
|
7
|
+
|
|
8
|
+
var auth: AuthContextType = null;
|
|
9
|
+
export function ConfigureGraphAuth(config?: AuthContextType) {
|
|
10
|
+
auth = config;
|
|
11
|
+
}
|
|
12
|
+
export async function getAxiosConfigGraph(tenantInfo: ITenantInfo, clearCache?: boolean) {
|
|
13
|
+
if (isNullOrUndefined(auth)) throw Error("Call ConfigureGraphAuth first");
|
|
14
|
+
|
|
15
|
+
// secret or certificate supported
|
|
16
|
+
let token = await GetMSALToken(tenantInfo, graphScope, auth, clearCache);
|
|
17
|
+
return getAxiosConfigBearer(token);
|
|
18
18
|
}
|
package/src/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./exports-index";
|
|
1
|
+
export * from "./exports-index";
|
package/src/storage/common.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import { isNullOrEmptyString } from "@kwiz/common";
|
|
2
|
-
import axios from "axios";
|
|
3
|
-
|
|
4
|
-
export async function IsAzuriteRunning() {
|
|
5
|
-
let responseServer = "";
|
|
6
|
-
try {
|
|
7
|
-
//make a request for http://127.0.0.1:10000/ expect response headers "Server" to contain "Azurite"
|
|
8
|
-
const result = await axios.get("http://127.0.0.1:10000/");
|
|
9
|
-
responseServer = result.headers.server;
|
|
10
|
-
} catch (e) {
|
|
11
|
-
responseServer = e.response && e.response.headers && e.response.headers.server;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
return isNullOrEmptyString(responseServer) ? false : responseServer.toLowerCase().indexOf("azurite") >= 0;
|
|
15
|
-
}
|
|
1
|
+
import { isNullOrEmptyString } from "@kwiz/common";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
|
|
4
|
+
export async function IsAzuriteRunning() {
|
|
5
|
+
let responseServer = "";
|
|
6
|
+
try {
|
|
7
|
+
//make a request for http://127.0.0.1:10000/ expect response headers "Server" to contain "Azurite"
|
|
8
|
+
const result = await axios.get("http://127.0.0.1:10000/");
|
|
9
|
+
responseServer = result.headers.server;
|
|
10
|
+
} catch (e) {
|
|
11
|
+
responseServer = e.response && e.response.headers && e.response.headers.server;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return isNullOrEmptyString(responseServer) ? false : responseServer.toLowerCase().indexOf("azurite") >= 0;
|
|
15
|
+
}
|
package/src/storage/odata.ts
CHANGED
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import { isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined } from "@kwiz/common";
|
|
2
|
-
|
|
3
|
-
export enum ODataOperators {
|
|
4
|
-
equal = "eq",
|
|
5
|
-
notEqual = "ne",
|
|
6
|
-
greater = "gt",
|
|
7
|
-
greaterOrEqual = "ge",
|
|
8
|
-
less = "lt",
|
|
9
|
-
lessOrEqual = "le",
|
|
10
|
-
contains = "in",
|
|
11
|
-
startswith = "startswith"
|
|
12
|
-
}
|
|
13
|
-
export enum ODataJoinOperators {
|
|
14
|
-
and = "and",
|
|
15
|
-
or = "or"
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface IOdataFilter<DataType> {
|
|
19
|
-
property: keyof DataType;
|
|
20
|
-
operator: ODataOperators;
|
|
21
|
-
value: string | Date | number | boolean;
|
|
22
|
-
not?: boolean;
|
|
23
|
-
}
|
|
24
|
-
export interface IOdataFilterStatement<DataType> {
|
|
25
|
-
filters: (IOdataFilter<DataType> | IOdataFilterStatement<DataType>)[];
|
|
26
|
-
/** default: or */
|
|
27
|
-
join?: ODataJoinOperators;
|
|
28
|
-
not?: boolean;
|
|
29
|
-
}
|
|
30
|
-
function isIOdataFilter<DataType>(f: IOdataFilter<DataType> | IOdataFilterStatement<DataType>): f is IOdataFilter<DataType> {
|
|
31
|
-
if (typeof (f as IOdataFilter<DataType>).property === "string")
|
|
32
|
-
return true;
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
function isIOdataFilterStatement<DataType>(f: IOdataFilter<DataType> | IOdataFilterStatement<DataType>): f is IOdataFilter<DataType> {
|
|
36
|
-
if (typeof (f as IOdataFilterStatement<DataType>).filters === "object" && Array.isArray((f as IOdataFilterStatement<DataType>).filters))
|
|
37
|
-
return true;
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
enum TableStorageKnownColumns {
|
|
42
|
-
partitionKey = "PartitionKey",
|
|
43
|
-
rowKey = "RowKey"
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function getPropNameForFilter(prop: string | number | symbol) {
|
|
47
|
-
return TableStorageKnownColumns[prop] || prop as string;
|
|
48
|
-
}
|
|
49
|
-
function getOdataFilterStatement<DataType>(filter: IOdataFilter<DataType>) {
|
|
50
|
-
let filterValue = isNullOrUndefined(filter.value)
|
|
51
|
-
? `null`
|
|
52
|
-
: typeof filter.value === "string"
|
|
53
|
-
? `'${filter.value.replace(/'/g, "''")}'`
|
|
54
|
-
: filter.value instanceof Date
|
|
55
|
-
? `'${filter.value.toISOString()}'`
|
|
56
|
-
: `${filter.value}`;
|
|
57
|
-
|
|
58
|
-
if (filter.operator === ODataOperators.startswith)
|
|
59
|
-
return `${filter.not ? 'not ' : ''}${filter.operator}(${getPropNameForFilter(filter.property)}, ${filterValue})`;
|
|
60
|
-
return `${filter.not ? 'not ' : ''}${getPropNameForFilter(filter.property)} ${filter.operator} ${filterValue}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function getOdataFilter<DataType>(statement: IOdataFilterStatement<DataType>) {
|
|
64
|
-
if (isNullOrUndefined(statement) || isNullOrEmptyArray(statement.filters)) return "";
|
|
65
|
-
if (isNullOrEmptyString(statement.join)) statement.join = ODataJoinOperators.or;
|
|
66
|
-
|
|
67
|
-
let filterStatements: string[] = [];
|
|
68
|
-
statement.filters.forEach(filter => {
|
|
69
|
-
if (isIOdataFilter(filter)) {
|
|
70
|
-
filterStatements.push(getOdataFilterStatement<DataType>(filter));
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
let subStatement = getOdataFilter(filter);
|
|
74
|
-
if (!isNullOrEmptyString(subStatement))
|
|
75
|
-
filterStatements.push(subStatement);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
if (filterStatements.length === 0) return "";
|
|
79
|
-
|
|
80
|
-
let result = "";
|
|
81
|
-
if (filterStatements.length === 1) result = filterStatements[0];
|
|
82
|
-
else if (filterStatements.length > 1) {
|
|
83
|
-
result = `(${filterStatements.join(` ${statement.join} `)})`;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return `${statement.not ? '(not ' : ''}${result}${statement.not ? ')' : ''}`;
|
|
1
|
+
import { isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined } from "@kwiz/common";
|
|
2
|
+
|
|
3
|
+
export enum ODataOperators {
|
|
4
|
+
equal = "eq",
|
|
5
|
+
notEqual = "ne",
|
|
6
|
+
greater = "gt",
|
|
7
|
+
greaterOrEqual = "ge",
|
|
8
|
+
less = "lt",
|
|
9
|
+
lessOrEqual = "le",
|
|
10
|
+
contains = "in",
|
|
11
|
+
startswith = "startswith"
|
|
12
|
+
}
|
|
13
|
+
export enum ODataJoinOperators {
|
|
14
|
+
and = "and",
|
|
15
|
+
or = "or"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface IOdataFilter<DataType> {
|
|
19
|
+
property: keyof DataType;
|
|
20
|
+
operator: ODataOperators;
|
|
21
|
+
value: string | Date | number | boolean;
|
|
22
|
+
not?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface IOdataFilterStatement<DataType> {
|
|
25
|
+
filters: (IOdataFilter<DataType> | IOdataFilterStatement<DataType>)[];
|
|
26
|
+
/** default: or */
|
|
27
|
+
join?: ODataJoinOperators;
|
|
28
|
+
not?: boolean;
|
|
29
|
+
}
|
|
30
|
+
function isIOdataFilter<DataType>(f: IOdataFilter<DataType> | IOdataFilterStatement<DataType>): f is IOdataFilter<DataType> {
|
|
31
|
+
if (typeof (f as IOdataFilter<DataType>).property === "string")
|
|
32
|
+
return true;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
function isIOdataFilterStatement<DataType>(f: IOdataFilter<DataType> | IOdataFilterStatement<DataType>): f is IOdataFilter<DataType> {
|
|
36
|
+
if (typeof (f as IOdataFilterStatement<DataType>).filters === "object" && Array.isArray((f as IOdataFilterStatement<DataType>).filters))
|
|
37
|
+
return true;
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
enum TableStorageKnownColumns {
|
|
42
|
+
partitionKey = "PartitionKey",
|
|
43
|
+
rowKey = "RowKey"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getPropNameForFilter(prop: string | number | symbol) {
|
|
47
|
+
return TableStorageKnownColumns[prop] || prop as string;
|
|
48
|
+
}
|
|
49
|
+
function getOdataFilterStatement<DataType>(filter: IOdataFilter<DataType>) {
|
|
50
|
+
let filterValue = isNullOrUndefined(filter.value)
|
|
51
|
+
? `null`
|
|
52
|
+
: typeof filter.value === "string"
|
|
53
|
+
? `'${filter.value.replace(/'/g, "''")}'`
|
|
54
|
+
: filter.value instanceof Date
|
|
55
|
+
? `'${filter.value.toISOString()}'`
|
|
56
|
+
: `${filter.value}`;
|
|
57
|
+
|
|
58
|
+
if (filter.operator === ODataOperators.startswith)
|
|
59
|
+
return `${filter.not ? 'not ' : ''}${filter.operator}(${getPropNameForFilter(filter.property)}, ${filterValue})`;
|
|
60
|
+
return `${filter.not ? 'not ' : ''}${getPropNameForFilter(filter.property)} ${filter.operator} ${filterValue}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getOdataFilter<DataType>(statement: IOdataFilterStatement<DataType>) {
|
|
64
|
+
if (isNullOrUndefined(statement) || isNullOrEmptyArray(statement.filters)) return "";
|
|
65
|
+
if (isNullOrEmptyString(statement.join)) statement.join = ODataJoinOperators.or;
|
|
66
|
+
|
|
67
|
+
let filterStatements: string[] = [];
|
|
68
|
+
statement.filters.forEach(filter => {
|
|
69
|
+
if (isIOdataFilter(filter)) {
|
|
70
|
+
filterStatements.push(getOdataFilterStatement<DataType>(filter));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
let subStatement = getOdataFilter(filter);
|
|
74
|
+
if (!isNullOrEmptyString(subStatement))
|
|
75
|
+
filterStatements.push(subStatement);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
if (filterStatements.length === 0) return "";
|
|
79
|
+
|
|
80
|
+
let result = "";
|
|
81
|
+
if (filterStatements.length === 1) result = filterStatements[0];
|
|
82
|
+
else if (filterStatements.length > 1) {
|
|
83
|
+
result = `(${filterStatements.join(` ${statement.join} `)})`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return `${statement.not ? '(not ' : ''}${result}${statement.not ? ')' : ''}`;
|
|
87
87
|
}
|
|
@@ -1,135 +1,135 @@
|
|
|
1
|
-
import assert from 'assert/strict';
|
|
2
|
-
import test from 'node:test';
|
|
3
|
-
import { IsAzuriteRunning } from "./common";
|
|
4
|
-
import { ODataOperators } from "./odata";
|
|
5
|
-
import { ConfigureTableStorage, Table, addItem, ensureTable } from "./table-storage";
|
|
6
|
-
|
|
7
|
-
test('testTableStorage', async t => {
|
|
8
|
-
if (!(await IsAzuriteRunning())) {
|
|
9
|
-
console.log('Skipping table-storage tests - azurite is not running.');
|
|
10
|
-
return;
|
|
11
|
-
}
|
|
12
|
-
//configure storage
|
|
13
|
-
ConfigureTableStorage({ connectionString: "UseDevelopmentStorage=true" });
|
|
14
|
-
|
|
15
|
-
const tableName = "TestTable";
|
|
16
|
-
const table = new Table(tableName, {
|
|
17
|
-
getKeys: p => p,
|
|
18
|
-
transform: {
|
|
19
|
-
load: v => ({ ...v, schedule: JSON.parse(v.schedule) }),
|
|
20
|
-
save: v => ({ ...v, schedule: JSON.stringify(v.schedule) })
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
const partitionKey = "PK1";
|
|
24
|
-
const partitionKey2 = "PK2";
|
|
25
|
-
const rowKey1 = "0b8612bb-377d-4897-be8f-a532a1ad3e73|d2e32b93-c096-431c-8a79-fcce9a4d7bce";
|
|
26
|
-
const rowKey2 = "0b8612bb-377d-4897-be8f-a532a1ad3e73|cd24aa15-0d03-4892-bb46-af32e5fb9361";
|
|
27
|
-
//start fresh
|
|
28
|
-
let result = await table.delete();
|
|
29
|
-
//don't test this - it might have a table to delete and might not... depending if cleanup didn't work last run
|
|
30
|
-
//await t.test("Delete table", t => assert.strictEqual(result, true));
|
|
31
|
-
|
|
32
|
-
//provoke table name not allowed
|
|
33
|
-
result = await ensureTable("T2");
|
|
34
|
-
await t.test("Table name not allowed", t => assert.strictEqual(result, false));
|
|
35
|
-
|
|
36
|
-
//create table
|
|
37
|
-
result = await table.ensure();
|
|
38
|
-
await t.test("Create table", t => assert.strictEqual(result, true));
|
|
39
|
-
|
|
40
|
-
//provoke table already exists
|
|
41
|
-
result = await table.ensure();
|
|
42
|
-
await t.test("Ensure existing table", t => assert.strictEqual(result, true));
|
|
43
|
-
|
|
44
|
-
//add item
|
|
45
|
-
result = await table.addItem({
|
|
46
|
-
partitionKey: partitionKey,
|
|
47
|
-
rowKey: rowKey1,
|
|
48
|
-
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
49
|
-
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
50
|
-
nextUTC: "2023010105",
|
|
51
|
-
schedule: { days: 4, hours: [1, 2, 3] }
|
|
52
|
-
});
|
|
53
|
-
await t.test("Create item 1", t => assert.strictEqual(result, true));
|
|
54
|
-
|
|
55
|
-
result = await table.addItem({
|
|
56
|
-
partitionKey: partitionKey,
|
|
57
|
-
rowKey: rowKey2,
|
|
58
|
-
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
59
|
-
action: "fab4094b-c000-4ff4-bb52-0f8538b2bc49",
|
|
60
|
-
nextUTC: "2023010105",
|
|
61
|
-
schedule: { days: 4, hours: [1, 2, 3] }
|
|
62
|
-
});
|
|
63
|
-
await t.test("Create item 2", t => assert.strictEqual(result, true));
|
|
64
|
-
|
|
65
|
-
result = await table.addItem({
|
|
66
|
-
partitionKey: partitionKey2,
|
|
67
|
-
rowKey: rowKey1,
|
|
68
|
-
list: "b98211e2-809a-4761-b928-559eddc28dfc",
|
|
69
|
-
action: "d20196e5-feb9-4202-bd03-718b49bf029e",
|
|
70
|
-
nextUTC: "2023010105",
|
|
71
|
-
schedule: { days: 4, hours: [1, 2, 3] }
|
|
72
|
-
});
|
|
73
|
-
await t.test("Create item in second partition", t => assert.strictEqual(result, true));
|
|
74
|
-
|
|
75
|
-
//provoke add second item with same key
|
|
76
|
-
result = await table.addItem({
|
|
77
|
-
partitionKey: partitionKey,
|
|
78
|
-
rowKey: rowKey1,
|
|
79
|
-
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
80
|
-
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
81
|
-
nextUTC: "2023010105",
|
|
82
|
-
schedule: { days: 4, hours: [1, 2, 3] }
|
|
83
|
-
});
|
|
84
|
-
await t.test("Create item with existing key", t => assert.strictEqual(result, false));
|
|
85
|
-
|
|
86
|
-
//provoke add second item with same key
|
|
87
|
-
result = await table.upsertItem({
|
|
88
|
-
partitionKey: partitionKey,
|
|
89
|
-
rowKey: rowKey1,
|
|
90
|
-
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
91
|
-
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
92
|
-
nextUTC: "2023020202",
|
|
93
|
-
schedule: { days: 4, hours: [1, 2, 3] }
|
|
94
|
-
});
|
|
95
|
-
await t.test("Update item 1", t => assert.strictEqual(result, true));
|
|
96
|
-
|
|
97
|
-
//provoke add item to table that does not exist
|
|
98
|
-
result = await addItem(tableName + "DoeNotExist", {
|
|
99
|
-
partitionKey: partitionKey,
|
|
100
|
-
rowKey: rowKey1,
|
|
101
|
-
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
102
|
-
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
103
|
-
nextUTC: "2023020202",
|
|
104
|
-
schedule: { days: 4, hours: [1, 2, 3] }
|
|
105
|
-
});
|
|
106
|
-
await t.test("Create item in table that does not exist", t => assert.strictEqual(result, false));
|
|
107
|
-
|
|
108
|
-
let allItems = await table.getItems();
|
|
109
|
-
await t.test("Get items", t => assert.strictEqual(allItems.length, 3));
|
|
110
|
-
|
|
111
|
-
//find items by partition
|
|
112
|
-
let partitionItems = await table.getItems({
|
|
113
|
-
filterStatment: {
|
|
114
|
-
filters: [
|
|
115
|
-
{
|
|
116
|
-
property: "partitionKey",
|
|
117
|
-
operator: ODataOperators.equal,
|
|
118
|
-
value: partitionKey
|
|
119
|
-
}
|
|
120
|
-
]
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
await t.test("Get items with filter", t => assert.strictEqual(partitionItems.length, 2));
|
|
125
|
-
await t.test("Update values", t => assert.strictEqual(partitionItems[1].nextUTC, "2023020202"));
|
|
126
|
-
|
|
127
|
-
let deleteResult = await table.deleteItem({ partitionKey: partitionKey, rowKey: rowKey1 });
|
|
128
|
-
await t.test("delete item", t => assert.strictEqual(deleteResult, true));
|
|
129
|
-
deleteResult = await table.deleteItem({ partitionKey: partitionKey, rowKey: rowKey1 });
|
|
130
|
-
await t.test("delete item that was already deleted", t => assert.strictEqual(deleteResult, true));
|
|
131
|
-
|
|
132
|
-
//cleanup
|
|
133
|
-
result = await table.delete();
|
|
134
|
-
await t.test("Delete table", t => assert.strictEqual(result, true));
|
|
1
|
+
import assert from 'assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { IsAzuriteRunning } from "./common";
|
|
4
|
+
import { ODataOperators } from "./odata";
|
|
5
|
+
import { ConfigureTableStorage, Table, addItem, ensureTable } from "./table-storage";
|
|
6
|
+
|
|
7
|
+
test('testTableStorage', async t => {
|
|
8
|
+
if (!(await IsAzuriteRunning())) {
|
|
9
|
+
console.log('Skipping table-storage tests - azurite is not running.');
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
//configure storage
|
|
13
|
+
ConfigureTableStorage({ connectionString: "UseDevelopmentStorage=true" });
|
|
14
|
+
|
|
15
|
+
const tableName = "TestTable";
|
|
16
|
+
const table = new Table(tableName, {
|
|
17
|
+
getKeys: p => p,
|
|
18
|
+
transform: {
|
|
19
|
+
load: v => ({ ...v, schedule: JSON.parse(v.schedule) }),
|
|
20
|
+
save: v => ({ ...v, schedule: JSON.stringify(v.schedule) })
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
const partitionKey = "PK1";
|
|
24
|
+
const partitionKey2 = "PK2";
|
|
25
|
+
const rowKey1 = "0b8612bb-377d-4897-be8f-a532a1ad3e73|d2e32b93-c096-431c-8a79-fcce9a4d7bce";
|
|
26
|
+
const rowKey2 = "0b8612bb-377d-4897-be8f-a532a1ad3e73|cd24aa15-0d03-4892-bb46-af32e5fb9361";
|
|
27
|
+
//start fresh
|
|
28
|
+
let result = await table.delete();
|
|
29
|
+
//don't test this - it might have a table to delete and might not... depending if cleanup didn't work last run
|
|
30
|
+
//await t.test("Delete table", t => assert.strictEqual(result, true));
|
|
31
|
+
|
|
32
|
+
//provoke table name not allowed
|
|
33
|
+
result = await ensureTable("T2");
|
|
34
|
+
await t.test("Table name not allowed", t => assert.strictEqual(result, false));
|
|
35
|
+
|
|
36
|
+
//create table
|
|
37
|
+
result = await table.ensure();
|
|
38
|
+
await t.test("Create table", t => assert.strictEqual(result, true));
|
|
39
|
+
|
|
40
|
+
//provoke table already exists
|
|
41
|
+
result = await table.ensure();
|
|
42
|
+
await t.test("Ensure existing table", t => assert.strictEqual(result, true));
|
|
43
|
+
|
|
44
|
+
//add item
|
|
45
|
+
result = await table.addItem({
|
|
46
|
+
partitionKey: partitionKey,
|
|
47
|
+
rowKey: rowKey1,
|
|
48
|
+
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
49
|
+
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
50
|
+
nextUTC: "2023010105",
|
|
51
|
+
schedule: { days: 4, hours: [1, 2, 3] }
|
|
52
|
+
});
|
|
53
|
+
await t.test("Create item 1", t => assert.strictEqual(result, true));
|
|
54
|
+
|
|
55
|
+
result = await table.addItem({
|
|
56
|
+
partitionKey: partitionKey,
|
|
57
|
+
rowKey: rowKey2,
|
|
58
|
+
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
59
|
+
action: "fab4094b-c000-4ff4-bb52-0f8538b2bc49",
|
|
60
|
+
nextUTC: "2023010105",
|
|
61
|
+
schedule: { days: 4, hours: [1, 2, 3] }
|
|
62
|
+
});
|
|
63
|
+
await t.test("Create item 2", t => assert.strictEqual(result, true));
|
|
64
|
+
|
|
65
|
+
result = await table.addItem({
|
|
66
|
+
partitionKey: partitionKey2,
|
|
67
|
+
rowKey: rowKey1,
|
|
68
|
+
list: "b98211e2-809a-4761-b928-559eddc28dfc",
|
|
69
|
+
action: "d20196e5-feb9-4202-bd03-718b49bf029e",
|
|
70
|
+
nextUTC: "2023010105",
|
|
71
|
+
schedule: { days: 4, hours: [1, 2, 3] }
|
|
72
|
+
});
|
|
73
|
+
await t.test("Create item in second partition", t => assert.strictEqual(result, true));
|
|
74
|
+
|
|
75
|
+
//provoke add second item with same key
|
|
76
|
+
result = await table.addItem({
|
|
77
|
+
partitionKey: partitionKey,
|
|
78
|
+
rowKey: rowKey1,
|
|
79
|
+
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
80
|
+
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
81
|
+
nextUTC: "2023010105",
|
|
82
|
+
schedule: { days: 4, hours: [1, 2, 3] }
|
|
83
|
+
});
|
|
84
|
+
await t.test("Create item with existing key", t => assert.strictEqual(result, false));
|
|
85
|
+
|
|
86
|
+
//provoke add second item with same key
|
|
87
|
+
result = await table.upsertItem({
|
|
88
|
+
partitionKey: partitionKey,
|
|
89
|
+
rowKey: rowKey1,
|
|
90
|
+
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
91
|
+
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
92
|
+
nextUTC: "2023020202",
|
|
93
|
+
schedule: { days: 4, hours: [1, 2, 3] }
|
|
94
|
+
});
|
|
95
|
+
await t.test("Update item 1", t => assert.strictEqual(result, true));
|
|
96
|
+
|
|
97
|
+
//provoke add item to table that does not exist
|
|
98
|
+
result = await addItem(tableName + "DoeNotExist", {
|
|
99
|
+
partitionKey: partitionKey,
|
|
100
|
+
rowKey: rowKey1,
|
|
101
|
+
list: "1bd32599-375e-4c98-9e1c-ce9dd2f2e17d",
|
|
102
|
+
action: "6b964dd7-6e62-42f6-89e4-5e79d6b6cbb0",
|
|
103
|
+
nextUTC: "2023020202",
|
|
104
|
+
schedule: { days: 4, hours: [1, 2, 3] }
|
|
105
|
+
});
|
|
106
|
+
await t.test("Create item in table that does not exist", t => assert.strictEqual(result, false));
|
|
107
|
+
|
|
108
|
+
let allItems = await table.getItems();
|
|
109
|
+
await t.test("Get items", t => assert.strictEqual(allItems.length, 3));
|
|
110
|
+
|
|
111
|
+
//find items by partition
|
|
112
|
+
let partitionItems = await table.getItems({
|
|
113
|
+
filterStatment: {
|
|
114
|
+
filters: [
|
|
115
|
+
{
|
|
116
|
+
property: "partitionKey",
|
|
117
|
+
operator: ODataOperators.equal,
|
|
118
|
+
value: partitionKey
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await t.test("Get items with filter", t => assert.strictEqual(partitionItems.length, 2));
|
|
125
|
+
await t.test("Update values", t => assert.strictEqual(partitionItems[1].nextUTC, "2023020202"));
|
|
126
|
+
|
|
127
|
+
let deleteResult = await table.deleteItem({ partitionKey: partitionKey, rowKey: rowKey1 });
|
|
128
|
+
await t.test("delete item", t => assert.strictEqual(deleteResult, true));
|
|
129
|
+
deleteResult = await table.deleteItem({ partitionKey: partitionKey, rowKey: rowKey1 });
|
|
130
|
+
await t.test("delete item that was already deleted", t => assert.strictEqual(deleteResult, true));
|
|
131
|
+
|
|
132
|
+
//cleanup
|
|
133
|
+
result = await table.delete();
|
|
134
|
+
await t.test("Delete table", t => assert.strictEqual(result, true));
|
|
135
135
|
});
|