@progressive-development/pd-provider-mock 0.9.0
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/dist/MockAuthProvider.d.ts +47 -0
- package/dist/MockAuthProvider.d.ts.map +1 -0
- package/dist/MockAuthProvider.js +161 -0
- package/dist/MockDatabaseProvider.d.ts +44 -0
- package/dist/MockDatabaseProvider.d.ts.map +1 -0
- package/dist/MockDatabaseProvider.js +196 -0
- package/dist/MockFunctionProvider.d.ts +43 -0
- package/dist/MockFunctionProvider.d.ts.map +1 -0
- package/dist/MockFunctionProvider.js +108 -0
- package/dist/MockStorageProvider.d.ts +43 -0
- package/dist/MockStorageProvider.d.ts.map +1 -0
- package/dist/MockStorageProvider.js +172 -0
- package/dist/createMockProvider.d.ts +87 -0
- package/dist/createMockProvider.d.ts.map +1 -0
- package/dist/createMockProvider.js +89 -0
- package/dist/example-fixtures.d.ts +38 -0
- package/dist/example-fixtures.d.ts.map +1 -0
- package/dist/example-fixtures.js +116 -0
- package/dist/fixtures.d.ts +142 -0
- package/dist/fixtures.d.ts.map +1 -0
- package/dist/fixtures.js +46 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/package.json +39 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { IAuthProvider, AuthUser, UserToken, AuthStateCallback, Unsubscribe } from '@progressive-development/pd-provider-interfaces';
|
|
2
|
+
import { MockUserFixture, MockProviderConfig } from './fixtures.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mock implementation of IAuthProvider
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - JSON fixture-based user configuration
|
|
8
|
+
* - Optional localStorage persistence
|
|
9
|
+
* - Configurable delays for realistic testing
|
|
10
|
+
* - Multiple user support
|
|
11
|
+
*/
|
|
12
|
+
export declare class MockAuthProvider implements IAuthProvider {
|
|
13
|
+
private users;
|
|
14
|
+
private currentUser;
|
|
15
|
+
private currentClaims;
|
|
16
|
+
private listeners;
|
|
17
|
+
private config;
|
|
18
|
+
constructor(users?: MockUserFixture[], config?: MockProviderConfig);
|
|
19
|
+
login(email: string, password: string): Promise<AuthUser>;
|
|
20
|
+
logout(): Promise<void>;
|
|
21
|
+
getCurrentUser(): AuthUser | null;
|
|
22
|
+
isAuthenticated(): boolean;
|
|
23
|
+
getIdToken(forceRefresh?: boolean): Promise<string | null>;
|
|
24
|
+
getIdTokenResult(forceRefresh?: boolean): Promise<UserToken | null>;
|
|
25
|
+
onAuthStateChanged(callback: AuthStateCallback): Unsubscribe;
|
|
26
|
+
sendPasswordResetEmail(email: string): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Add a user fixture at runtime
|
|
29
|
+
*/
|
|
30
|
+
addUser(userFixture: MockUserFixture): void;
|
|
31
|
+
/**
|
|
32
|
+
* Set the current user directly (for testing)
|
|
33
|
+
*/
|
|
34
|
+
setCurrentUser(user: AuthUser | null, claims?: Record<string, unknown>): void;
|
|
35
|
+
/**
|
|
36
|
+
* Get all configured users
|
|
37
|
+
*/
|
|
38
|
+
getConfiguredUsers(): MockUserFixture[];
|
|
39
|
+
private notifyListeners;
|
|
40
|
+
private delay;
|
|
41
|
+
private log;
|
|
42
|
+
private get storageKey();
|
|
43
|
+
private persistToStorage;
|
|
44
|
+
private restoreFromStorage;
|
|
45
|
+
private clearStorage;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=MockAuthProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockAuthProvider.d.ts","sourceRoot":"","sources":["../src/MockAuthProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,aAAa,EACb,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,WAAW,EACZ,MAAM,iDAAiD,CAAC;AAEzD,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAIzE;;;;;;;;GAQG;AACH,qBAAa,gBAAiB,YAAW,aAAa;IACpD,OAAO,CAAC,KAAK,CAAoB;IACjC,OAAO,CAAC,WAAW,CAAyB;IAC5C,OAAO,CAAC,aAAa,CAA+B;IACpD,OAAO,CAAC,SAAS,CAAqC;IACtD,OAAO,CAAC,MAAM,CAAqB;gBAEvB,KAAK,GAAE,eAAe,EAAO,EAAE,MAAM,GAAE,kBAAuB;IAUpE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAiCzD,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB7B,cAAc,IAAI,QAAQ,GAAG,IAAI;IAIjC,eAAe,IAAI,OAAO;IAIpB,UAAU,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAaxD,gBAAgB,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAoBvE,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,WAAW;IAWtD,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1D;;OAEG;IACH,OAAO,CAAC,WAAW,EAAE,eAAe,GAAG,IAAI;IAI3C;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAM7E;;OAEG;IACH,kBAAkB,IAAI,eAAe,EAAE;IAQvC,OAAO,CAAC,eAAe;YAIT,KAAK;IAOnB,OAAO,CAAC,GAAG;IAMX,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,CAAC,gBAAgB;IAYxB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,YAAY;CAOrB"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const DEFAULT_PERSIST_KEY = "pd-mock-auth";
|
|
2
|
+
class MockAuthProvider {
|
|
3
|
+
constructor(users = [], config = {}) {
|
|
4
|
+
this.currentUser = null;
|
|
5
|
+
this.currentClaims = {};
|
|
6
|
+
this.listeners = /* @__PURE__ */ new Set();
|
|
7
|
+
this.users = users;
|
|
8
|
+
this.config = config;
|
|
9
|
+
if (config.persistAuth) {
|
|
10
|
+
this.restoreFromStorage();
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
async login(email, password) {
|
|
14
|
+
this.log(`Login attempt for: ${email}`);
|
|
15
|
+
await this.delay();
|
|
16
|
+
const userFixture = this.users.find(
|
|
17
|
+
(u) => u.email === email && u.password === password
|
|
18
|
+
);
|
|
19
|
+
if (!userFixture) {
|
|
20
|
+
const error = new Error("Invalid credentials");
|
|
21
|
+
error.code = "auth/wrong-password";
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
this.currentUser = userFixture.user;
|
|
25
|
+
this.currentClaims = userFixture.claims || {};
|
|
26
|
+
if (this.config.persistAuth) {
|
|
27
|
+
this.persistToStorage();
|
|
28
|
+
}
|
|
29
|
+
this.notifyListeners();
|
|
30
|
+
this.log(`Login successful for: ${email}`);
|
|
31
|
+
return this.currentUser;
|
|
32
|
+
}
|
|
33
|
+
async logout() {
|
|
34
|
+
this.log("Logout");
|
|
35
|
+
await this.delay();
|
|
36
|
+
this.currentUser = null;
|
|
37
|
+
this.currentClaims = {};
|
|
38
|
+
if (this.config.persistAuth) {
|
|
39
|
+
this.clearStorage();
|
|
40
|
+
}
|
|
41
|
+
this.notifyListeners();
|
|
42
|
+
}
|
|
43
|
+
getCurrentUser() {
|
|
44
|
+
return this.currentUser;
|
|
45
|
+
}
|
|
46
|
+
isAuthenticated() {
|
|
47
|
+
return this.currentUser !== null;
|
|
48
|
+
}
|
|
49
|
+
async getIdToken(forceRefresh = false) {
|
|
50
|
+
if (!this.currentUser) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
if (forceRefresh) {
|
|
54
|
+
await this.delay(100);
|
|
55
|
+
}
|
|
56
|
+
return `mock-token-${this.currentUser.uid}-${Date.now()}`;
|
|
57
|
+
}
|
|
58
|
+
async getIdTokenResult(forceRefresh = false) {
|
|
59
|
+
if (!this.currentUser) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
if (forceRefresh) {
|
|
63
|
+
await this.delay(100);
|
|
64
|
+
}
|
|
65
|
+
const token = await this.getIdToken();
|
|
66
|
+
const expirationDate = /* @__PURE__ */ new Date();
|
|
67
|
+
expirationDate.setHours(expirationDate.getHours() + 1);
|
|
68
|
+
return {
|
|
69
|
+
token,
|
|
70
|
+
claims: this.currentClaims,
|
|
71
|
+
expirationTime: expirationDate.toISOString()
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
onAuthStateChanged(callback) {
|
|
75
|
+
this.listeners.add(callback);
|
|
76
|
+
callback(this.currentUser);
|
|
77
|
+
return () => {
|
|
78
|
+
this.listeners.delete(callback);
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async sendPasswordResetEmail(email) {
|
|
82
|
+
this.log(`Password reset email "sent" to: ${email}`);
|
|
83
|
+
await this.delay();
|
|
84
|
+
}
|
|
85
|
+
// -------------------------------------------------------------------------
|
|
86
|
+
// Mock-specific Methods
|
|
87
|
+
// -------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Add a user fixture at runtime
|
|
90
|
+
*/
|
|
91
|
+
addUser(userFixture) {
|
|
92
|
+
this.users.push(userFixture);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Set the current user directly (for testing)
|
|
96
|
+
*/
|
|
97
|
+
setCurrentUser(user, claims) {
|
|
98
|
+
this.currentUser = user;
|
|
99
|
+
this.currentClaims = claims || {};
|
|
100
|
+
this.notifyListeners();
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get all configured users
|
|
104
|
+
*/
|
|
105
|
+
getConfiguredUsers() {
|
|
106
|
+
return [...this.users];
|
|
107
|
+
}
|
|
108
|
+
// -------------------------------------------------------------------------
|
|
109
|
+
// Private Helpers
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
notifyListeners() {
|
|
112
|
+
this.listeners.forEach((callback) => callback(this.currentUser));
|
|
113
|
+
}
|
|
114
|
+
async delay(ms) {
|
|
115
|
+
const delayMs = ms ?? this.config.defaultDelay ?? 500;
|
|
116
|
+
if (delayMs > 0) {
|
|
117
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
log(message) {
|
|
121
|
+
if (this.config.logging) {
|
|
122
|
+
console.log(`[MockAuth] ${message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
get storageKey() {
|
|
126
|
+
return this.config.persistAuthKey || DEFAULT_PERSIST_KEY;
|
|
127
|
+
}
|
|
128
|
+
persistToStorage() {
|
|
129
|
+
try {
|
|
130
|
+
const data = {
|
|
131
|
+
user: this.currentUser,
|
|
132
|
+
claims: this.currentClaims
|
|
133
|
+
};
|
|
134
|
+
localStorage.setItem(this.storageKey, JSON.stringify(data));
|
|
135
|
+
} catch (e) {
|
|
136
|
+
this.log(`Failed to persist auth: ${e}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
restoreFromStorage() {
|
|
140
|
+
try {
|
|
141
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
142
|
+
if (stored) {
|
|
143
|
+
const data = JSON.parse(stored);
|
|
144
|
+
this.currentUser = data.user;
|
|
145
|
+
this.currentClaims = data.claims || {};
|
|
146
|
+
this.log(`Restored auth from storage: ${this.currentUser?.email}`);
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
this.log(`Failed to restore auth: ${e}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
clearStorage() {
|
|
153
|
+
try {
|
|
154
|
+
localStorage.removeItem(this.storageKey);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
this.log(`Failed to clear auth storage: ${e}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export { MockAuthProvider };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { IDatabaseProvider, QueryConstraint, DocumentSnapshot, CollectionCallback, Unsubscribe } from '@progressive-development/pd-provider-interfaces';
|
|
2
|
+
import { MockDatabaseCollection, MockDatabaseDocument, MockProviderConfig } from './fixtures.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mock implementation of IDatabaseProvider
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - JSON fixture-based collection data
|
|
8
|
+
* - In-memory document operations
|
|
9
|
+
* - Collection listeners with real-time updates
|
|
10
|
+
* - Query constraint filtering
|
|
11
|
+
*/
|
|
12
|
+
export declare class MockDatabaseProvider implements IDatabaseProvider {
|
|
13
|
+
private collections;
|
|
14
|
+
private subscriptions;
|
|
15
|
+
private config;
|
|
16
|
+
constructor(fixtures?: MockDatabaseCollection[], config?: MockProviderConfig);
|
|
17
|
+
onCollection<T = unknown>(collectionPath: string, callback: CollectionCallback<T>, constraints?: QueryConstraint[]): Unsubscribe;
|
|
18
|
+
getDocument<T = unknown>(collectionPath: string, documentId: string): Promise<DocumentSnapshot<T>>;
|
|
19
|
+
setDocument<T = unknown>(collectionPath: string, documentId: string, data: T): Promise<void>;
|
|
20
|
+
updateDocument(collectionPath: string, documentId: string, data: Record<string, unknown>): Promise<void>;
|
|
21
|
+
deleteDocument(collectionPath: string, documentId: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Add a collection fixture at runtime
|
|
24
|
+
*/
|
|
25
|
+
addCollection(path: string, documents: MockDatabaseDocument[]): void;
|
|
26
|
+
/**
|
|
27
|
+
* Clear a collection
|
|
28
|
+
*/
|
|
29
|
+
clearCollection(path: string): void;
|
|
30
|
+
/**
|
|
31
|
+
* Clear all collections
|
|
32
|
+
*/
|
|
33
|
+
clearAllCollections(): void;
|
|
34
|
+
/**
|
|
35
|
+
* Get all documents in a collection (without filtering)
|
|
36
|
+
*/
|
|
37
|
+
getAllDocuments<T>(collectionPath: string): MockDatabaseDocument<T>[];
|
|
38
|
+
private getFilteredDocuments;
|
|
39
|
+
private matchesConstraints;
|
|
40
|
+
private notifySubscribers;
|
|
41
|
+
private delay;
|
|
42
|
+
private log;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=MockDatabaseProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockDatabaseProvider.d.ts","sourceRoot":"","sources":["../src/MockDatabaseProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACZ,MAAM,iDAAiD,CAAC;AAEzD,OAAO,KAAK,EACV,sBAAsB,EACtB,oBAAoB,EACpB,kBAAkB,EACnB,MAAM,eAAe,CAAC;AAWvB;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,YAAW,iBAAiB;IAC5D,OAAO,CAAC,WAAW,CAAkD;IACrE,OAAO,CAAC,aAAa,CAAmD;IACxE,OAAO,CAAC,MAAM,CAAqB;gBAGjC,QAAQ,GAAE,sBAAsB,EAAO,EACvC,MAAM,GAAE,kBAAuB;IAUjC,YAAY,CAAC,CAAC,GAAG,OAAO,EACtB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC/B,WAAW,CAAC,EAAE,eAAe,EAAE,GAC9B,WAAW;IAqBR,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3B,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;IAezB,WAAW,CAAC,CAAC,GAAG,OAAO,EAC3B,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,CAAC,GACN,OAAO,CAAC,IAAI,CAAC;IAuBV,cAAc,CAClB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC;IAsBV,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuB/E;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,oBAAoB,EAAE,GAAG,IAAI;IAKpE;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAKnC;;OAEG;IACH,mBAAmB,IAAI,IAAI;IAM3B;;OAEG;IACH,eAAe,CAAC,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,oBAAoB,CAAC,CAAC,CAAC,EAAE;IAQrE,OAAO,CAAC,oBAAoB;IAoB5B,OAAO,CAAC,kBAAkB;IAyD1B,OAAO,CAAC,iBAAiB;YAYX,KAAK;IAOnB,OAAO,CAAC,GAAG;CAKZ"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
class MockDatabaseProvider {
|
|
2
|
+
constructor(fixtures = [], config = {}) {
|
|
3
|
+
this.collections = /* @__PURE__ */ new Map();
|
|
4
|
+
this.subscriptions = /* @__PURE__ */ new Set();
|
|
5
|
+
this.config = config;
|
|
6
|
+
for (const fixture of fixtures) {
|
|
7
|
+
this.collections.set(fixture.path, [...fixture.documents]);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
onCollection(collectionPath, callback, constraints) {
|
|
11
|
+
this.log(`Subscribing to collection: ${collectionPath}`);
|
|
12
|
+
const subscription = {
|
|
13
|
+
path: collectionPath,
|
|
14
|
+
callback,
|
|
15
|
+
constraints
|
|
16
|
+
};
|
|
17
|
+
this.subscriptions.add(subscription);
|
|
18
|
+
const documents = this.getFilteredDocuments(collectionPath, constraints);
|
|
19
|
+
callback(documents);
|
|
20
|
+
return () => {
|
|
21
|
+
this.subscriptions.delete(subscription);
|
|
22
|
+
this.log(`Unsubscribed from collection: ${collectionPath}`);
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async getDocument(collectionPath, documentId) {
|
|
26
|
+
this.log(`Getting document: ${collectionPath}/${documentId}`);
|
|
27
|
+
await this.delay();
|
|
28
|
+
const collection = this.collections.get(collectionPath) || [];
|
|
29
|
+
const doc = collection.find((d) => d.id === documentId);
|
|
30
|
+
return {
|
|
31
|
+
id: documentId,
|
|
32
|
+
data: doc?.data ?? {},
|
|
33
|
+
exists: !!doc
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async setDocument(collectionPath, documentId, data) {
|
|
37
|
+
this.log(`Setting document: ${collectionPath}/${documentId}`);
|
|
38
|
+
await this.delay();
|
|
39
|
+
let collection = this.collections.get(collectionPath);
|
|
40
|
+
if (!collection) {
|
|
41
|
+
collection = [];
|
|
42
|
+
this.collections.set(collectionPath, collection);
|
|
43
|
+
}
|
|
44
|
+
const existingIndex = collection.findIndex((d) => d.id === documentId);
|
|
45
|
+
if (existingIndex >= 0) {
|
|
46
|
+
collection[existingIndex] = { id: documentId, data };
|
|
47
|
+
} else {
|
|
48
|
+
collection.push({ id: documentId, data });
|
|
49
|
+
}
|
|
50
|
+
this.notifySubscribers(collectionPath);
|
|
51
|
+
}
|
|
52
|
+
async updateDocument(collectionPath, documentId, data) {
|
|
53
|
+
this.log(`Updating document: ${collectionPath}/${documentId}`);
|
|
54
|
+
await this.delay();
|
|
55
|
+
const collection = this.collections.get(collectionPath);
|
|
56
|
+
if (!collection) {
|
|
57
|
+
throw new Error(`Collection not found: ${collectionPath}`);
|
|
58
|
+
}
|
|
59
|
+
const doc = collection.find((d) => d.id === documentId);
|
|
60
|
+
if (!doc) {
|
|
61
|
+
throw new Error(`Document not found: ${collectionPath}/${documentId}`);
|
|
62
|
+
}
|
|
63
|
+
doc.data = { ...doc.data, ...data };
|
|
64
|
+
this.notifySubscribers(collectionPath);
|
|
65
|
+
}
|
|
66
|
+
async deleteDocument(collectionPath, documentId) {
|
|
67
|
+
this.log(`Deleting document: ${collectionPath}/${documentId}`);
|
|
68
|
+
await this.delay();
|
|
69
|
+
const collection = this.collections.get(collectionPath);
|
|
70
|
+
if (!collection) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const index = collection.findIndex((d) => d.id === documentId);
|
|
74
|
+
if (index >= 0) {
|
|
75
|
+
collection.splice(index, 1);
|
|
76
|
+
}
|
|
77
|
+
this.notifySubscribers(collectionPath);
|
|
78
|
+
}
|
|
79
|
+
// -------------------------------------------------------------------------
|
|
80
|
+
// Mock-specific Methods
|
|
81
|
+
// -------------------------------------------------------------------------
|
|
82
|
+
/**
|
|
83
|
+
* Add a collection fixture at runtime
|
|
84
|
+
*/
|
|
85
|
+
addCollection(path, documents) {
|
|
86
|
+
this.collections.set(path, [...documents]);
|
|
87
|
+
this.notifySubscribers(path);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Clear a collection
|
|
91
|
+
*/
|
|
92
|
+
clearCollection(path) {
|
|
93
|
+
this.collections.delete(path);
|
|
94
|
+
this.notifySubscribers(path);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Clear all collections
|
|
98
|
+
*/
|
|
99
|
+
clearAllCollections() {
|
|
100
|
+
const paths = [...this.collections.keys()];
|
|
101
|
+
this.collections.clear();
|
|
102
|
+
paths.forEach((path) => this.notifySubscribers(path));
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get all documents in a collection (without filtering)
|
|
106
|
+
*/
|
|
107
|
+
getAllDocuments(collectionPath) {
|
|
108
|
+
return this.collections.get(collectionPath) || [];
|
|
109
|
+
}
|
|
110
|
+
// -------------------------------------------------------------------------
|
|
111
|
+
// Private Helpers
|
|
112
|
+
// -------------------------------------------------------------------------
|
|
113
|
+
getFilteredDocuments(collectionPath, constraints) {
|
|
114
|
+
let documents = this.collections.get(collectionPath) || [];
|
|
115
|
+
if (constraints && constraints.length > 0) {
|
|
116
|
+
documents = documents.filter(
|
|
117
|
+
(doc) => this.matchesConstraints(doc.data, constraints)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return documents.map((doc) => ({
|
|
121
|
+
id: doc.id,
|
|
122
|
+
data: doc.data,
|
|
123
|
+
exists: true
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
matchesConstraints(data, constraints) {
|
|
127
|
+
for (const constraint of constraints) {
|
|
128
|
+
if (constraint.type !== "where") {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const fieldValue = data[constraint.field];
|
|
132
|
+
const targetValue = constraint.value;
|
|
133
|
+
let matches = false;
|
|
134
|
+
switch (constraint.operator) {
|
|
135
|
+
case "==":
|
|
136
|
+
matches = fieldValue === targetValue;
|
|
137
|
+
break;
|
|
138
|
+
case "!=":
|
|
139
|
+
matches = fieldValue !== targetValue;
|
|
140
|
+
break;
|
|
141
|
+
case "<":
|
|
142
|
+
matches = fieldValue < targetValue;
|
|
143
|
+
break;
|
|
144
|
+
case "<=":
|
|
145
|
+
matches = fieldValue <= targetValue;
|
|
146
|
+
break;
|
|
147
|
+
case ">":
|
|
148
|
+
matches = fieldValue > targetValue;
|
|
149
|
+
break;
|
|
150
|
+
case ">=":
|
|
151
|
+
matches = fieldValue >= targetValue;
|
|
152
|
+
break;
|
|
153
|
+
case "array-contains":
|
|
154
|
+
matches = Array.isArray(fieldValue) && fieldValue.includes(targetValue);
|
|
155
|
+
break;
|
|
156
|
+
case "array-contains-any":
|
|
157
|
+
matches = Array.isArray(fieldValue) && Array.isArray(targetValue) && targetValue.some((v) => fieldValue.includes(v));
|
|
158
|
+
break;
|
|
159
|
+
case "in":
|
|
160
|
+
matches = Array.isArray(targetValue) && targetValue.includes(fieldValue);
|
|
161
|
+
break;
|
|
162
|
+
case "not-in":
|
|
163
|
+
matches = Array.isArray(targetValue) && !targetValue.includes(fieldValue);
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
if (!matches) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
notifySubscribers(collectionPath) {
|
|
173
|
+
for (const subscription of this.subscriptions) {
|
|
174
|
+
if (subscription.path === collectionPath) {
|
|
175
|
+
const documents = this.getFilteredDocuments(
|
|
176
|
+
collectionPath,
|
|
177
|
+
subscription.constraints
|
|
178
|
+
);
|
|
179
|
+
subscription.callback(documents);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async delay() {
|
|
184
|
+
const delayMs = this.config.defaultDelay ?? 100;
|
|
185
|
+
if (delayMs > 0) {
|
|
186
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
log(message) {
|
|
190
|
+
if (this.config.logging) {
|
|
191
|
+
console.log(`[MockDatabase] ${message}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export { MockDatabaseProvider };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { IFunctionProvider, FunctionResult } from '@progressive-development/pd-provider-interfaces';
|
|
2
|
+
import { MockFunctionFixture, MockProviderConfig } from './fixtures.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mock implementation of IFunctionProvider
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - JSON fixture-based function responses
|
|
8
|
+
* - Input matching for conditional responses
|
|
9
|
+
* - Configurable delays
|
|
10
|
+
* - Error simulation
|
|
11
|
+
*/
|
|
12
|
+
export declare class MockFunctionProvider implements IFunctionProvider {
|
|
13
|
+
private fixtures;
|
|
14
|
+
private config;
|
|
15
|
+
private region;
|
|
16
|
+
constructor(fixtures?: MockFunctionFixture[], config?: MockProviderConfig);
|
|
17
|
+
callFunction(functionName: string, data: unknown): Promise<FunctionResult>;
|
|
18
|
+
setRegion(region: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Add a function fixture at runtime
|
|
21
|
+
*/
|
|
22
|
+
addFixture(fixture: MockFunctionFixture): void;
|
|
23
|
+
/**
|
|
24
|
+
* Remove all fixtures for a function
|
|
25
|
+
*/
|
|
26
|
+
removeFixtures(functionName: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Clear all fixtures
|
|
29
|
+
*/
|
|
30
|
+
clearFixtures(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Set fixtures (replace all)
|
|
33
|
+
*/
|
|
34
|
+
setFixtures(fixtures: MockFunctionFixture[]): void;
|
|
35
|
+
/**
|
|
36
|
+
* Get configured region
|
|
37
|
+
*/
|
|
38
|
+
getRegion(): string;
|
|
39
|
+
private findMatchingFixture;
|
|
40
|
+
private matchesInput;
|
|
41
|
+
private log;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=MockFunctionProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockFunctionProvider.d.ts","sourceRoot":"","sources":["../src/MockFunctionProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACf,MAAM,iDAAiD,CAAC;AAEzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE7E;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,YAAW,iBAAiB;IAC5D,OAAO,CAAC,QAAQ,CAAwB;IACxC,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAiB;gBAEnB,QAAQ,GAAE,mBAAmB,EAAO,EAAE,MAAM,GAAE,kBAAuB;IAK3E,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,cAAc,CAAC;IA8BhF,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAS/B;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IAI9C;;OAEG;IACH,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI1C;;OAEG;IACH,aAAa,IAAI,IAAI;IAIrB;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,IAAI;IAIlD;;OAEG;IACH,SAAS,IAAI,MAAM;IAQnB,OAAO,CAAC,mBAAmB;IA6B3B,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,GAAG;CASZ"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
class MockFunctionProvider {
|
|
2
|
+
constructor(fixtures = [], config = {}) {
|
|
3
|
+
this.region = "mock-region";
|
|
4
|
+
this.fixtures = fixtures;
|
|
5
|
+
this.config = config;
|
|
6
|
+
}
|
|
7
|
+
async callFunction(functionName, data) {
|
|
8
|
+
this.log(`Calling function: ${functionName}`, data);
|
|
9
|
+
const fixture = this.findMatchingFixture(functionName, data);
|
|
10
|
+
if (!fixture) {
|
|
11
|
+
this.log(`No fixture found for: ${functionName}`);
|
|
12
|
+
throw new Error(`Mock: No fixture configured for function "${functionName}"`);
|
|
13
|
+
}
|
|
14
|
+
const delay = fixture.response.delay ?? this.config.defaultDelay ?? 500;
|
|
15
|
+
if (delay > 0) {
|
|
16
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
17
|
+
}
|
|
18
|
+
if (fixture.response.error) {
|
|
19
|
+
const error = new Error(fixture.response.error.message);
|
|
20
|
+
if (fixture.response.error.code) {
|
|
21
|
+
error.code = fixture.response.error.code;
|
|
22
|
+
}
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
this.log(`Returning result for: ${functionName}`, fixture.response.result);
|
|
26
|
+
return fixture.response.result;
|
|
27
|
+
}
|
|
28
|
+
setRegion(region) {
|
|
29
|
+
this.region = region;
|
|
30
|
+
this.log(`Region set to: ${region}`);
|
|
31
|
+
}
|
|
32
|
+
// -------------------------------------------------------------------------
|
|
33
|
+
// Mock-specific Methods
|
|
34
|
+
// -------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Add a function fixture at runtime
|
|
37
|
+
*/
|
|
38
|
+
addFixture(fixture) {
|
|
39
|
+
this.fixtures.push(fixture);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Remove all fixtures for a function
|
|
43
|
+
*/
|
|
44
|
+
removeFixtures(functionName) {
|
|
45
|
+
this.fixtures = this.fixtures.filter((f) => f.name !== functionName);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Clear all fixtures
|
|
49
|
+
*/
|
|
50
|
+
clearFixtures() {
|
|
51
|
+
this.fixtures = [];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Set fixtures (replace all)
|
|
55
|
+
*/
|
|
56
|
+
setFixtures(fixtures) {
|
|
57
|
+
this.fixtures = fixtures;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get configured region
|
|
61
|
+
*/
|
|
62
|
+
getRegion() {
|
|
63
|
+
return this.region;
|
|
64
|
+
}
|
|
65
|
+
// -------------------------------------------------------------------------
|
|
66
|
+
// Private Helpers
|
|
67
|
+
// -------------------------------------------------------------------------
|
|
68
|
+
findMatchingFixture(functionName, data) {
|
|
69
|
+
const matchingByName = this.fixtures.filter((f) => f.name === functionName);
|
|
70
|
+
if (matchingByName.length === 0) {
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
if (matchingByName.length === 1 && !matchingByName[0].inputMatcher) {
|
|
74
|
+
return matchingByName[0];
|
|
75
|
+
}
|
|
76
|
+
const withMatcher = matchingByName.filter((f) => f.inputMatcher);
|
|
77
|
+
for (const fixture of withMatcher) {
|
|
78
|
+
if (this.matchesInput(fixture.inputMatcher, data)) {
|
|
79
|
+
return fixture;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const withoutMatcher = matchingByName.find((f) => !f.inputMatcher);
|
|
83
|
+
return withoutMatcher;
|
|
84
|
+
}
|
|
85
|
+
matchesInput(matcher, data) {
|
|
86
|
+
if (!data || typeof data !== "object") {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const dataObj = data;
|
|
90
|
+
for (const [key, value] of Object.entries(matcher)) {
|
|
91
|
+
if (dataObj[key] !== value) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
log(message, data) {
|
|
98
|
+
if (this.config.logging) {
|
|
99
|
+
if (data !== void 0) {
|
|
100
|
+
console.log(`[MockFunction] ${message}`, data);
|
|
101
|
+
} else {
|
|
102
|
+
console.log(`[MockFunction] ${message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { MockFunctionProvider };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { IStorageProvider, UploadFileRequest, UploadResult, StorageDocument, ListFilesOptions, GetFileOptions } from '@progressive-development/pd-provider-interfaces';
|
|
2
|
+
import { MockStorageFixture, MockStorageFile, MockProviderConfig } from './fixtures.js';
|
|
3
|
+
/**
|
|
4
|
+
* Mock implementation of IStorageProvider
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - JSON fixture-based file configuration
|
|
8
|
+
* - In-memory upload storage
|
|
9
|
+
* - Configurable delays
|
|
10
|
+
*/
|
|
11
|
+
export declare class MockStorageProvider implements IStorageProvider {
|
|
12
|
+
private fixtures;
|
|
13
|
+
private config;
|
|
14
|
+
/** In-memory storage for uploaded files */
|
|
15
|
+
private uploadedFiles;
|
|
16
|
+
constructor(fixtures?: MockStorageFixture[], config?: MockProviderConfig);
|
|
17
|
+
uploadFile(request: UploadFileRequest): Promise<UploadResult>;
|
|
18
|
+
downloadFile(options: GetFileOptions): Promise<string>;
|
|
19
|
+
deleteFile(options: GetFileOptions): Promise<void>;
|
|
20
|
+
listFiles(options: ListFilesOptions): Promise<StorageDocument[]>;
|
|
21
|
+
getFile(options: GetFileOptions): Promise<StorageDocument | null>;
|
|
22
|
+
/**
|
|
23
|
+
* Add a storage fixture at runtime
|
|
24
|
+
*/
|
|
25
|
+
addFixture(fixture: MockStorageFixture): void;
|
|
26
|
+
/**
|
|
27
|
+
* Clear all uploaded files (in-memory)
|
|
28
|
+
*/
|
|
29
|
+
clearUploads(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Get all uploaded files
|
|
32
|
+
*/
|
|
33
|
+
getUploadedFiles(): Map<string, MockStorageFile>;
|
|
34
|
+
/**
|
|
35
|
+
* Set fixtures (replace all)
|
|
36
|
+
*/
|
|
37
|
+
setFixtures(fixtures: MockStorageFixture[]): void;
|
|
38
|
+
private buildFilePath;
|
|
39
|
+
private findFileInFixtures;
|
|
40
|
+
private delay;
|
|
41
|
+
private log;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=MockStorageProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MockStorageProvider.d.ts","sourceRoot":"","sources":["../src/MockStorageProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,cAAc,EACf,MAAM,iDAAiD,CAAC;AAEzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE7F;;;;;;;GAOG;AACH,qBAAa,mBAAoB,YAAW,gBAAgB;IAC1D,OAAO,CAAC,QAAQ,CAAuB;IACvC,OAAO,CAAC,MAAM,CAAqB;IACnC,2CAA2C;IAC3C,OAAO,CAAC,aAAa,CAA2C;gBAEpD,QAAQ,GAAE,kBAAkB,EAAO,EAAE,MAAM,GAAE,kBAAuB;IAK1E,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC;IAwC7D,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BtD,UAAU,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBlD,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IA8ChE,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IA2BvE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAI7C;;OAEG;IACH,YAAY,IAAI,IAAI;IAIpB;;OAEG;IACH,gBAAgB,IAAI,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC;IAIhD;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,kBAAkB,EAAE,GAAG,IAAI;IAQjD,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,kBAAkB;YAmBZ,KAAK;IAOnB,OAAO,CAAC,GAAG;CAKZ"}
|