@memberjunction/testing-integration 0.0.0 → 5.49.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.
Files changed (66) hide show
  1. package/dist/IntegrationTestDriver.d.ts +63 -0
  2. package/dist/IntegrationTestDriver.d.ts.map +1 -0
  3. package/dist/IntegrationTestDriver.js +412 -0
  4. package/dist/IntegrationTestDriver.js.map +1 -0
  5. package/dist/ai-verify.d.ts +27 -0
  6. package/dist/ai-verify.d.ts.map +1 -0
  7. package/dist/ai-verify.js +120 -0
  8. package/dist/ai-verify.js.map +1 -0
  9. package/dist/bootstrap-client.d.ts +11 -0
  10. package/dist/bootstrap-client.d.ts.map +1 -0
  11. package/dist/bootstrap-client.js +68 -0
  12. package/dist/bootstrap-client.js.map +1 -0
  13. package/dist/bootstrap-shared.d.ts +94 -0
  14. package/dist/bootstrap-shared.d.ts.map +1 -0
  15. package/dist/bootstrap-shared.js +94 -0
  16. package/dist/bootstrap-shared.js.map +1 -0
  17. package/dist/bootstrap.d.ts +14 -0
  18. package/dist/bootstrap.d.ts.map +1 -0
  19. package/dist/bootstrap.js +133 -0
  20. package/dist/bootstrap.js.map +1 -0
  21. package/dist/check-registry.d.ts +39 -0
  22. package/dist/check-registry.d.ts.map +1 -0
  23. package/dist/check-registry.js +61 -0
  24. package/dist/check-registry.js.map +1 -0
  25. package/dist/check.d.ts +531 -0
  26. package/dist/check.d.ts.map +1 -0
  27. package/dist/check.js +2 -0
  28. package/dist/check.js.map +1 -0
  29. package/dist/checks/self-test.check.d.ts +2 -0
  30. package/dist/checks/self-test.check.d.ts.map +1 -0
  31. package/dist/checks/self-test.check.js +35 -0
  32. package/dist/checks/self-test.check.js.map +1 -0
  33. package/dist/config.d.ts +35 -0
  34. package/dist/config.d.ts.map +1 -0
  35. package/dist/config.js +81 -0
  36. package/dist/config.js.map +1 -0
  37. package/dist/index.d.ts +25 -0
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +37 -0
  40. package/dist/index.js.map +1 -0
  41. package/dist/instrumented-cache.d.ts +47 -0
  42. package/dist/instrumented-cache.d.ts.map +1 -0
  43. package/dist/instrumented-cache.js +81 -0
  44. package/dist/instrumented-cache.js.map +1 -0
  45. package/dist/registry.d.ts +33 -0
  46. package/dist/registry.d.ts.map +1 -0
  47. package/dist/registry.js +37 -0
  48. package/dist/registry.js.map +1 -0
  49. package/dist/rls-fixture.d.ts +37 -0
  50. package/dist/rls-fixture.d.ts.map +1 -0
  51. package/dist/rls-fixture.js +91 -0
  52. package/dist/rls-fixture.js.map +1 -0
  53. package/dist/test-runner.d.ts +55 -0
  54. package/dist/test-runner.d.ts.map +1 -0
  55. package/dist/test-runner.js +128 -0
  56. package/dist/test-runner.js.map +1 -0
  57. package/dist/tiers.d.ts +28 -0
  58. package/dist/tiers.d.ts.map +1 -0
  59. package/dist/tiers.js +41 -0
  60. package/dist/tiers.js.map +1 -0
  61. package/dist/types.d.ts +51 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +2 -0
  64. package/dist/types.js.map +1 -0
  65. package/package.json +59 -8
  66. package/README.md +0 -45
@@ -0,0 +1,35 @@
1
+ /**
2
+ * self-test.check.ts — the ONE check registered by Phase 0.
3
+ *
4
+ * It is not part of any migrated suite; it exists solely to give Phase 0 a green,
5
+ * end-to-end "the bootstrap installed an instrumented cache that actually sees
6
+ * RunView traffic" proof — the exact failure mode the harness warns about (cache
7
+ * installed AFTER provider setup is a silent no-op). Retain it as a permanent smoke
8
+ * check or remove at the team's discretion.
9
+ */
10
+ import { RunView } from '@memberjunction/core';
11
+ import { IntegrationCheckRegistry } from '../check-registry.js';
12
+ import { Assert } from '../test-runner.js';
13
+ IntegrationCheckRegistry.Instance.Register({
14
+ Id: 'self-test.cache-warm',
15
+ Name: 'Self-test: instrumented cache observes RunView traffic',
16
+ Fn: async (ctx) => {
17
+ ctx.Storage.ResetCounts();
18
+ const rv = new RunView();
19
+ // A cold, per-run-unique fingerprint forces a cache MISS (one RunViewCache write).
20
+ // Column-AGNOSTIC always-true predicate — the rls-isolation bundle documented why:
21
+ // UniqueFilter('ID', tag) feeds a non-GUID literal to the uniqueidentifier PK and
22
+ // errors. A pure string-literal comparison is always true and valid for any entity;
23
+ // the timestamp tag makes each run a distinct cold slot.
24
+ const r = await rv.RunView({
25
+ EntityName: 'MJ: Users',
26
+ ExtraFilter: `'p0-selftest-${Date.now()}' <> 'zzz-cache-test-marker'`,
27
+ Fields: ['ID'],
28
+ ResultType: 'simple'
29
+ }, ctx.User);
30
+ const writes = ctx.Storage.SetCount('RunViewCache');
31
+ Assert(r.Success, `RunView failed: ${r.ErrorMessage}`);
32
+ Assert(writes >= 1, `Expected >=1 RunViewCache write on cold miss, saw ${writes}`);
33
+ }
34
+ });
35
+ //# sourceMappingURL=self-test.check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"self-test.check.js","sourceRoot":"","sources":["../../src/checks/self-test.check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAC/C,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,wBAAwB,CAAC,QAAQ,CAAC,QAAQ,CAAC;IACvC,EAAE,EAAE,sBAAsB;IAC1B,IAAI,EAAE,wDAAwD;IAC9D,EAAE,EAAE,KAAK,EAAE,GAAG,EAAiB,EAAE;QAC7B,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC1B,MAAM,EAAE,GAAG,IAAI,OAAO,EAAE,CAAC;QACzB,mFAAmF;QACnF,mFAAmF;QACnF,kFAAkF;QAClF,oFAAoF;QACpF,yDAAyD;QACzD,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,OAAO,CAAiB;YACvC,UAAU,EAAE,WAAW;YACvB,WAAW,EAAE,gBAAgB,IAAI,CAAC,GAAG,EAAE,8BAA8B;YACrE,MAAM,EAAE,CAAC,IAAI,CAAC;YACd,UAAU,EAAE,QAAQ;SACvB,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;QACpD,MAAM,CAAC,CAAC,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,qDAAqD,MAAM,EAAE,CAAC,CAAC;IACvF,CAAC;CACJ,CAAC,CAAC"}
@@ -0,0 +1,35 @@
1
+ /** The database backend the integration run targets. Mirrors the repo-wide DB_PLATFORM convention. */
2
+ export type IntegrationDbPlatform = 'sqlserver' | 'postgresql';
3
+ /**
4
+ * Loads the repo-root .env into process.env. Must be called from a process whose
5
+ * cwd is the repository root (documented usage for the integration scripts).
6
+ */
7
+ export declare function LoadEnv(): void;
8
+ export interface DbConfig {
9
+ Host: string;
10
+ Port: number;
11
+ User: string;
12
+ Password: string;
13
+ Database: string;
14
+ Schema: string;
15
+ /** Which backend to bootstrap. Resolved from DB_PLATFORM (default 'sqlserver'). */
16
+ Platform: IntegrationDbPlatform;
17
+ }
18
+ /**
19
+ * Resolves database settings from mj.config.cjs (databaseSettings) with .env
20
+ * fallbacks. Throws with a clear message when required settings are missing.
21
+ */
22
+ export declare function LoadDbConfig(): Promise<DbConfig>;
23
+ export interface ClientConfig {
24
+ Url: string;
25
+ WsUrl: string;
26
+ MJAPIKey: string;
27
+ }
28
+ /**
29
+ * Resolves the MJAPI GraphQL endpoint and system API key from env:
30
+ * - MJAPI_URL overrides everything; otherwise http://localhost:{GRAPHQL_PORT}{GRAPHQL_ROOT_PATH}
31
+ * - MJAPI_WS_URL overrides the subscription endpoint; otherwise derived from Url (http→ws, https→wss)
32
+ * - MJ_API_KEY is the system API key MJServer accepts via the x-mj-api-key header
33
+ */
34
+ export declare function LoadClientConfig(): ClientConfig;
35
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA,sGAAsG;AACtG,MAAM,MAAM,qBAAqB,GAAG,WAAW,GAAG,YAAY,CAAC;AAmC/D;;;GAGG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAE9B;AAED,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,QAAQ,EAAE,qBAAqB,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CA2BtD;AAED,MAAM,WAAW,YAAY;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,CAU/C"}
package/dist/config.js ADDED
@@ -0,0 +1,81 @@
1
+ /**
2
+ * config.ts — environment / database / client configuration resolution for the
3
+ * integration-test bootstrap. Lifted verbatim from the original live harness
4
+ * (packages/MJServer/integration-test-scripts/lib/harness.ts) so the standalone
5
+ * tsx scripts and the IntegrationTestDriver resolve settings identically.
6
+ *
7
+ * No hardcoded secrets: everything comes from the repo-root .env / mj.config.cjs.
8
+ */
9
+ import dotenv from 'dotenv';
10
+ import path from 'path';
11
+ import { cosmiconfig } from 'cosmiconfig';
12
+ /**
13
+ * Resolve the target backend from DB_PLATFORM. Mirrors the canonical
14
+ * resolveDbPlatformFromEnv (@memberjunction/generic-database-provider) but kept inline
15
+ * so the lightweight config layer doesn't pull that package's module-load cost in just
16
+ * to read one env var. Strict: only 'sqlserver' / 'postgresql' (case-insensitive),
17
+ * default 'sqlserver', throws on anything else (legacy aliases are not accepted).
18
+ */
19
+ function resolveIntegrationDbPlatform() {
20
+ const raw = process.env.DB_PLATFORM;
21
+ if (raw === undefined || raw.trim() === '')
22
+ return 'sqlserver';
23
+ const normalized = raw.trim().toLowerCase();
24
+ if (normalized === 'sqlserver' || normalized === 'postgresql')
25
+ return normalized;
26
+ throw new Error(`Invalid DB_PLATFORM value '${raw}'. Must be 'sqlserver' or 'postgresql' (case-insensitive).`);
27
+ }
28
+ /**
29
+ * Loads the repo-root .env into process.env. Must be called from a process whose
30
+ * cwd is the repository root (documented usage for the integration scripts).
31
+ */
32
+ export function LoadEnv() {
33
+ dotenv.config({ path: path.resolve(process.cwd(), '.env'), quiet: true });
34
+ }
35
+ /**
36
+ * Resolves database settings from mj.config.cjs (databaseSettings) with .env
37
+ * fallbacks. Throws with a clear message when required settings are missing.
38
+ */
39
+ export async function LoadDbConfig() {
40
+ const configResult = await cosmiconfig('mj').search();
41
+ const config = (configResult?.config ?? {});
42
+ const dbSettings = config.databaseSettings ?? {};
43
+ const host = dbSettings.host || process.env.DB_HOST;
44
+ const user = dbSettings.user || process.env.DB_USERNAME;
45
+ const password = dbSettings.password || process.env.DB_PASSWORD;
46
+ const database = dbSettings.database || process.env.DB_DATABASE;
47
+ if (!host || !user || !password || !database) {
48
+ throw new Error('Missing DB settings. Provide them via mj.config.cjs databaseSettings or .env ' +
49
+ '(DB_HOST, DB_USERNAME, DB_PASSWORD, DB_DATABASE). Run from the repo root.');
50
+ }
51
+ // DB_PLATFORM ∈ {sqlserver, postgresql}; throws on invalid values.
52
+ const platform = resolveIntegrationDbPlatform();
53
+ const defaultPort = platform === 'postgresql' ? 5432 : 1433;
54
+ return {
55
+ Host: host,
56
+ Port: Number(dbSettings.port ?? process.env.DB_PORT ?? defaultPort),
57
+ User: user,
58
+ Password: password,
59
+ Database: database,
60
+ Schema: config.mjCoreSchema || dbSettings.mjCoreSchema || '__mj',
61
+ Platform: platform
62
+ };
63
+ }
64
+ /**
65
+ * Resolves the MJAPI GraphQL endpoint and system API key from env:
66
+ * - MJAPI_URL overrides everything; otherwise http://localhost:{GRAPHQL_PORT}{GRAPHQL_ROOT_PATH}
67
+ * - MJAPI_WS_URL overrides the subscription endpoint; otherwise derived from Url (http→ws, https→wss)
68
+ * - MJ_API_KEY is the system API key MJServer accepts via the x-mj-api-key header
69
+ */
70
+ export function LoadClientConfig() {
71
+ const apiKey = process.env.MJ_API_KEY;
72
+ if (!apiKey) {
73
+ throw new Error('MJ_API_KEY is not set in the environment — required for client-side tests.');
74
+ }
75
+ const port = process.env.GRAPHQL_PORT ?? '4000';
76
+ const rootPath = process.env.GRAPHQL_ROOT_PATH ?? '/';
77
+ const url = process.env.MJAPI_URL ?? `http://localhost:${port}${rootPath.startsWith('/') ? rootPath : `/${rootPath}`}`;
78
+ const wsUrl = process.env.MJAPI_WS_URL ?? url.replace(/^http/, 'ws');
79
+ return { Url: url, WsUrl: wsUrl, MJAPIKey: apiKey };
80
+ }
81
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @memberjunction/testing-integration — public API.
3
+ *
4
+ * Defines this package's surface only (no re-exports of other packages' symbols;
5
+ * InMemoryLocalStorageProvider etc. are imported from @memberjunction/core where used).
6
+ */
7
+ export * from './config.js';
8
+ export * from './tiers.js';
9
+ export * from './instrumented-cache.js';
10
+ export * from './test-runner.js';
11
+ export * from './bootstrap.js';
12
+ export * from './bootstrap-client.js';
13
+ export * from './ai-verify.js';
14
+ export * from './check.js';
15
+ export * from './check-registry.js';
16
+ import './checks/self-test.check.js';
17
+ export * from './rls-fixture.js';
18
+ export * from './types.js';
19
+ export * from './IntegrationTestDriver.js';
20
+ /**
21
+ * Tree-shake guard. Importing this module (or calling this function) ensures the
22
+ * check registrations and the IntegrationTestDriver @RegisterClass decorator have run.
23
+ */
24
+ export declare function LoadTestingIntegration(): void;
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAI5B,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,OAAO,0BAA0B,CAAC;AAClC,cAAc,eAAe,CAAC;AAI9B,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC;AAQxC;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C"}
package/dist/index.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @memberjunction/testing-integration — public API.
3
+ *
4
+ * Defines this package's surface only (no re-exports of other packages' symbols;
5
+ * InMemoryLocalStorageProvider etc. are imported from @memberjunction/core where used).
6
+ */
7
+ export * from './config.js';
8
+ export * from './tiers.js';
9
+ export * from './instrumented-cache.js';
10
+ export * from './test-runner.js';
11
+ export * from './bootstrap.js';
12
+ // bootstrapIntegrationClient lives in the server-FREE bootstrap-client module (so client
13
+ // dispatchers can import it via the `./client` subpath without dragging in server packages).
14
+ // Re-exported here for backward-compat barrel consumers (the driver / server dispatchers).
15
+ export * from './bootstrap-client.js';
16
+ export * from './ai-verify.js';
17
+ export * from './check.js';
18
+ export * from './check-registry.js';
19
+ import './checks/self-test.check.js'; // side effect: register the permanent Phase-0 smoke check
20
+ export * from './rls-fixture.js';
21
+ // NOTE: the check BUNDLES (MJ's own test content) live in the private
22
+ // @memberjunction/integration-test-suite package — importing THAT package registers
23
+ // every bundle on this registry. This framework package deliberately ships content-free.
24
+ export * from './types.js';
25
+ export * from './IntegrationTestDriver.js';
26
+ // Re-export the bundle arrays + RunQuery fixture helpers AND, as a side effect of
27
+ // evaluating these modules, register every bundled check on the IntegrationCheckRegistry.
28
+ // The @RegisterClass decorator on IntegrationTestDriver fires via the export above.
29
+ // Side-effect only: the permanent Phase-0 smoke check (no exports of its own).
30
+ /**
31
+ * Tree-shake guard. Importing this module (or calling this function) ensures the
32
+ * check registrations and the IntegrationTestDriver @RegisterClass decorator have run.
33
+ */
34
+ export function LoadTestingIntegration() {
35
+ /* no-op — importing this module is the side effect */
36
+ }
37
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,cAAc,UAAU,CAAC;AACzB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,yFAAyF;AACzF,6FAA6F;AAC7F,2FAA2F;AAC3F,cAAc,oBAAoB,CAAC;AACnC,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,OAAO,0BAA0B,CAAC,CAAC,0DAA0D;AAC7F,cAAc,eAAe,CAAC;AAC9B,sEAAsE;AACtE,oFAAoF;AACpF,yFAAyF;AACzF,cAAc,SAAS,CAAC;AACxB,cAAc,yBAAyB,CAAC;AAExC,kFAAkF;AAClF,0FAA0F;AAC1F,oFAAoF;AAEpF,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,UAAU,sBAAsB;IAClC,sDAAsD;AAC1D,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * instrumented-cache.ts — the InstrumentedLocalStorageProvider and UniqueFilter,
3
+ * lifted verbatim from the original live harness. These are the two net-new
4
+ * primitives this package introduces; everything else they touch
5
+ * (InMemoryLocalStorageProvider, ILocalStorageProvider) is imported from
6
+ * @memberjunction/core, never copied.
7
+ */
8
+ import type { ILocalStorageProvider } from '@memberjunction/core';
9
+ /**
10
+ * Wraps any ILocalStorageProvider with call counters so tests can prove cache
11
+ * behavior from the outside: a cache WRITE shows up as SetItemCount++, a cache
12
+ * READ as GetItemCount/GetItemsCount++, and "served without touching storage"
13
+ * (e.g. a dedup/linger hit) as no counter movement at all.
14
+ */
15
+ export declare class InstrumentedLocalStorageProvider implements ILocalStorageProvider {
16
+ private readonly inner;
17
+ GetItemCount: number;
18
+ GetItemsCount: number;
19
+ SetItemCount: number;
20
+ RemoveCount: number;
21
+ private perCategory;
22
+ constructor(inner: ILocalStorageProvider);
23
+ ResetCounts(): void;
24
+ /**
25
+ * Per-category counters — IMPORTANT for assertions: LocalCacheManager also
26
+ * persists its registry index asynchronously (a different category), so tests
27
+ * about RunView cache traffic must scope to the 'RunViewCache' category rather
28
+ * than the global counters.
29
+ */
30
+ GetCount(category: string): number;
31
+ SetCount(category: string): number;
32
+ private bump;
33
+ GetItem<T = unknown>(key: string, category?: string): Promise<T | null>;
34
+ GetItems<T = unknown>(keys: string[], category?: string): Promise<Map<string, T | null>>;
35
+ SetItem<T>(key: string, value: T, category?: string): Promise<void>;
36
+ Remove(key: string, category?: string): Promise<void>;
37
+ ClearCategory(category: string): Promise<void>;
38
+ GetCategoryKeys(category: string): Promise<string[]>;
39
+ }
40
+ /**
41
+ * Returns an always-true ExtraFilter that is textually unique per tag — every tag
42
+ * yields a distinct cache fingerprint (Filter is part of the fingerprint) while
43
+ * matching the same rows. This lets each check start from a guaranteed-cold cache
44
+ * entry without mutating any data.
45
+ */
46
+ export declare function UniqueFilter(column: string, tag: string): string;
47
+ //# sourceMappingURL=instrumented-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumented-cache.d.ts","sourceRoot":"","sources":["../src/instrumented-cache.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE;;;;;GAKG;AACH,qBAAa,gCAAiC,YAAW,qBAAqB;IAO9D,OAAO,CAAC,QAAQ,CAAC,KAAK;IAN3B,YAAY,SAAK;IACjB,aAAa,SAAK;IAClB,YAAY,SAAK;IACjB,WAAW,SAAK;IACvB,OAAO,CAAC,WAAW,CAAqD;gBAE3C,KAAK,EAAE,qBAAqB;IAElD,WAAW,IAAI,IAAI;IAQ1B;;;;;OAKG;IACI,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIlC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAIzC,OAAO,CAAC,IAAI;IAOC,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAMvE,QAAQ,CAAC,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAMxF,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMnE,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAM9C,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAMpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhE"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Wraps any ILocalStorageProvider with call counters so tests can prove cache
3
+ * behavior from the outside: a cache WRITE shows up as SetItemCount++, a cache
4
+ * READ as GetItemCount/GetItemsCount++, and "served without touching storage"
5
+ * (e.g. a dedup/linger hit) as no counter movement at all.
6
+ */
7
+ export class InstrumentedLocalStorageProvider {
8
+ constructor(inner) {
9
+ this.inner = inner;
10
+ this.GetItemCount = 0;
11
+ this.GetItemsCount = 0;
12
+ this.SetItemCount = 0;
13
+ this.RemoveCount = 0;
14
+ this.perCategory = new Map();
15
+ }
16
+ ResetCounts() {
17
+ this.GetItemCount = 0;
18
+ this.GetItemsCount = 0;
19
+ this.SetItemCount = 0;
20
+ this.RemoveCount = 0;
21
+ this.perCategory.clear();
22
+ }
23
+ /**
24
+ * Per-category counters — IMPORTANT for assertions: LocalCacheManager also
25
+ * persists its registry index asynchronously (a different category), so tests
26
+ * about RunView cache traffic must scope to the 'RunViewCache' category rather
27
+ * than the global counters.
28
+ */
29
+ GetCount(category) {
30
+ return this.perCategory.get(category)?.Gets ?? 0;
31
+ }
32
+ SetCount(category) {
33
+ return this.perCategory.get(category)?.Sets ?? 0;
34
+ }
35
+ bump(category, kind) {
36
+ const key = category ?? 'default';
37
+ const entry = this.perCategory.get(key) ?? { Gets: 0, Sets: 0 };
38
+ entry[kind]++;
39
+ this.perCategory.set(key, entry);
40
+ }
41
+ async GetItem(key, category) {
42
+ this.GetItemCount++;
43
+ this.bump(category, 'Gets');
44
+ return this.inner.GetItem(key, category);
45
+ }
46
+ async GetItems(keys, category) {
47
+ this.GetItemsCount++;
48
+ this.bump(category, 'Gets');
49
+ return this.inner.GetItems(keys, category);
50
+ }
51
+ async SetItem(key, value, category) {
52
+ this.SetItemCount++;
53
+ this.bump(category, 'Sets');
54
+ return this.inner.SetItem(key, value, category);
55
+ }
56
+ async Remove(key, category) {
57
+ this.RemoveCount++;
58
+ return this.inner.Remove(key, category);
59
+ }
60
+ async ClearCategory(category) {
61
+ if (this.inner.ClearCategory) {
62
+ return this.inner.ClearCategory(category);
63
+ }
64
+ }
65
+ async GetCategoryKeys(category) {
66
+ if (this.inner.GetCategoryKeys) {
67
+ return this.inner.GetCategoryKeys(category);
68
+ }
69
+ return [];
70
+ }
71
+ }
72
+ /**
73
+ * Returns an always-true ExtraFilter that is textually unique per tag — every tag
74
+ * yields a distinct cache fingerprint (Filter is part of the fingerprint) while
75
+ * matching the same rows. This lets each check start from a guaranteed-cold cache
76
+ * entry without mutating any data.
77
+ */
78
+ export function UniqueFilter(column, tag) {
79
+ return `${column} <> 'zzz-cache-test-${tag}'`;
80
+ }
81
+ //# sourceMappingURL=instrumented-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrumented-cache.js","sourceRoot":"","sources":["../src/instrumented-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * registry.ts — the SERVER-FREE entry point for check registration + execution.
3
+ *
4
+ * WHY THIS EXISTS (browser fidelity, B32):
5
+ * The root barrel (`./index`) re-exports `./bootstrap` (which imports
6
+ * `@memberjunction/server-bootstrap-lite` + `mssql`) and every check module, some of
7
+ * which import server-only packages. So a CLIENT dispatcher that reached for
8
+ * `TestRunner` / `IntegrationCheckRegistry` through the barrel transitively loaded
9
+ * `@memberjunction/core-entities-server` — and the ClassFactory then resolved entities
10
+ * to their SERVER subclasses. The symptom was visible but easy to miss: the process
11
+ * logged `MJRemoteOperationEntityServer is server/database-only …` and
12
+ * `GetEntityObject('MJ: Tag Scopes')` returned `MJTagScopeEntityServer` rather than the
13
+ * client class a browser would get.
14
+ *
15
+ * That silently defeated the point of client-first testing: the bundle claimed to prove
16
+ * the wire behaves correctly for a browser, while actually exercising server subclasses
17
+ * a browser never loads.
18
+ *
19
+ * This module therefore exports ONLY client-safe primitives — the runner, the registry,
20
+ * and the types. It imports NO bootstrap and NO check modules. A client dispatcher pairs
21
+ * it with `@memberjunction/testing-integration/client` (the server-free bootstrap) and a
22
+ * direct side-effect import of its OWN check bundle via the `./checks/*` subpath, so the
23
+ * only checks registered are the ones it intends to run.
24
+ *
25
+ * Keep this file dependency-light on purpose: anything added here is loaded by every
26
+ * client dispatcher, and a single server import would silently reintroduce B32.
27
+ */
28
+ export * from './config.js';
29
+ export * from './test-runner.js';
30
+ export * from './check-registry.js';
31
+ export * from './check.js';
32
+ export * from './tiers.js';
33
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * registry.ts — the SERVER-FREE entry point for check registration + execution.
3
+ *
4
+ * WHY THIS EXISTS (browser fidelity, B32):
5
+ * The root barrel (`./index`) re-exports `./bootstrap` (which imports
6
+ * `@memberjunction/server-bootstrap-lite` + `mssql`) and every check module, some of
7
+ * which import server-only packages. So a CLIENT dispatcher that reached for
8
+ * `TestRunner` / `IntegrationCheckRegistry` through the barrel transitively loaded
9
+ * `@memberjunction/core-entities-server` — and the ClassFactory then resolved entities
10
+ * to their SERVER subclasses. The symptom was visible but easy to miss: the process
11
+ * logged `MJRemoteOperationEntityServer is server/database-only …` and
12
+ * `GetEntityObject('MJ: Tag Scopes')` returned `MJTagScopeEntityServer` rather than the
13
+ * client class a browser would get.
14
+ *
15
+ * That silently defeated the point of client-first testing: the bundle claimed to prove
16
+ * the wire behaves correctly for a browser, while actually exercising server subclasses
17
+ * a browser never loads.
18
+ *
19
+ * This module therefore exports ONLY client-safe primitives — the runner, the registry,
20
+ * and the types. It imports NO bootstrap and NO check modules. A client dispatcher pairs
21
+ * it with `@memberjunction/testing-integration/client` (the server-free bootstrap) and a
22
+ * direct side-effect import of its OWN check bundle via the `./checks/*` subpath, so the
23
+ * only checks registered are the ones it intends to run.
24
+ *
25
+ * Keep this file dependency-light on purpose: anything added here is loaded by every
26
+ * client dispatcher, and a single server import would silently reintroduce B32.
27
+ */
28
+ // All four are verified server-free: `config` pulls only dotenv/path/cosmiconfig,
29
+ // `test-runner` only node:fs/node:path, `tiers` nothing, and `check-registry` only
30
+ // @memberjunction/global. `check`'s every import — including `mssql` and the
31
+ // instrumented-cache provider — is `import type`, so all of it is erased at compile time.
32
+ export * from './config.js';
33
+ export * from './test-runner.js';
34
+ export * from './check-registry.js';
35
+ export * from './check.js';
36
+ export * from './tiers.js';
37
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,kFAAkF;AAClF,mFAAmF;AACnF,6EAA6E;AAC7E,0FAA0F;AAC1F,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { UserInfo, IMetadataProvider, RowLevelSecurityFilterInfo } from '@memberjunction/core';
2
+ import { RlsFixture } from './check.js';
3
+ /**
4
+ * Emails of the purpose-built RLS test users seeded via version-controlled metadata. These
5
+ * principals (users + the "Integration Test: RLS Scoped Reader" role + its entity-permission grant)
6
+ * live in the SIBLING `metadata-optional/integration-test/` root — NOT the default-pushed `metadata/`
7
+ * tree — so they never land in a production DB that only syncs `metadata/`. A/B are each in
8
+ * ONLY that role → genuinely scoped (non-exempt) on SEEDED_RLS_ENTITY; the no-grant user has no
9
+ * roles. Kept in sync with the fixtures by convention (change both together).
10
+ */
11
+ export declare const SEEDED_SCOPED_A_EMAIL = "it-rls-a@integration.test";
12
+ export declare const SEEDED_SCOPED_B_EMAIL = "it-rls-b@integration.test";
13
+ export declare const SEEDED_NOGRANT_EMAIL = "it-nogrant@integration.test";
14
+ /** The entity the seeded scoped role grants read on (with the `{{UserID}}` RLS filter). */
15
+ export declare const SEEDED_RLS_ENTITY = "MJ: AI Agent Runs";
16
+ /** The exact command that seeds the RLS principals — printed in every skip-as-pass warning below. */
17
+ export declare const SEED_FIXTURES_COMMAND = "npx mj sync push --dir=metadata-optional/integration-test";
18
+ /** Case-insensitive user-by-email lookup for the seeded fixtures. */
19
+ export declare function findUserByEmail(users: UserInfo[], email: string): UserInfo | undefined;
20
+ /** Discover a `{{UserID}}`-scoped RLS filter from the provider (for the token-based checks). */
21
+ export declare function discoverTokenFilter(provider: IMetadataProvider): RowLevelSecurityFilterInfo | undefined;
22
+ /** Discover the first non-exempt (user, entity) pair — a user with a non-empty Read clause. */
23
+ export declare function discoverLivePair(provider: IMetadataProvider, users: UserInfo[]): {
24
+ User: UserInfo;
25
+ EntityName: string;
26
+ } | undefined;
27
+ /**
28
+ * Discover the RLS fixture: a two-user divergent-clause pair PLUS the two independent
29
+ * single-user pieces (TokenFilter, LivePair). Pure (no singletons) so it is unit-testable
30
+ * with a mocked provider + synthetic users. The two-user discovery iterates entities,
31
+ * computes each user's clause once per entity (O(entities × users)), and returns the first
32
+ * entity where two distinct users get different non-empty clauses. TokenFilter/LivePair are
33
+ * attached to every returned fixture regardless of two-user usability, so the single-user
34
+ * checks (RLS1/RLS5) run even when the DB lacks two divergent users.
35
+ */
36
+ export declare function discoverRlsFixture(provider: IMetadataProvider, users: UserInfo[]): RlsFixture;
37
+ //# sourceMappingURL=rls-fixture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-fixture.d.ts","sourceRoot":"","sources":["../src/rls-fixture.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAEpG,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AACjE,eAAO,MAAM,qBAAqB,8BAA8B,CAAC;AACjE,eAAO,MAAM,oBAAoB,gCAAgC,CAAC;AAClE,2FAA2F;AAC3F,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,qGAAqG;AACrG,eAAO,MAAM,qBAAqB,8DAA8D,CAAC;AAEjG,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAEtF;AAED,gGAAgG;AAChG,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,0BAA0B,GAAG,SAAS,CAGvG;AAED,+FAA+F;AAC/F,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAUnI;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,iBAAiB,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,UAAU,CAmC7F"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * rls-fixture.ts — RLS fixture DISCOVERY (framework-side).
3
+ *
4
+ * Extracted from the rls-isolation check bundle when the bundles moved to the private
5
+ * @memberjunction/integration-test-suite package: IntegrationTestDriver provisions the
6
+ * suite-scoped RlsFixture itself (before dispatching bundles), so discovery must live in
7
+ * the FRAMEWORK, not in content the framework cannot depend on. Pure (no singletons):
8
+ * unit-testable with a mocked provider + synthetic users. The rls-isolation bundle imports
9
+ * these same helpers back from here.
10
+ */
11
+ import { EntityPermissionType } from '@memberjunction/core';
12
+ import { UUIDsEqual } from '@memberjunction/global';
13
+ /**
14
+ * Emails of the purpose-built RLS test users seeded via version-controlled metadata. These
15
+ * principals (users + the "Integration Test: RLS Scoped Reader" role + its entity-permission grant)
16
+ * live in the SIBLING `metadata-optional/integration-test/` root — NOT the default-pushed `metadata/`
17
+ * tree — so they never land in a production DB that only syncs `metadata/`. A/B are each in
18
+ * ONLY that role → genuinely scoped (non-exempt) on SEEDED_RLS_ENTITY; the no-grant user has no
19
+ * roles. Kept in sync with the fixtures by convention (change both together).
20
+ */
21
+ export const SEEDED_SCOPED_A_EMAIL = 'it-rls-a@integration.test';
22
+ export const SEEDED_SCOPED_B_EMAIL = 'it-rls-b@integration.test';
23
+ export const SEEDED_NOGRANT_EMAIL = 'it-nogrant@integration.test';
24
+ /** The entity the seeded scoped role grants read on (with the `{{UserID}}` RLS filter). */
25
+ export const SEEDED_RLS_ENTITY = 'MJ: AI Agent Runs';
26
+ /** The exact command that seeds the RLS principals — printed in every skip-as-pass warning below. */
27
+ export const SEED_FIXTURES_COMMAND = 'npx mj sync push --dir=metadata-optional/integration-test';
28
+ /** Case-insensitive user-by-email lookup for the seeded fixtures. */
29
+ export function findUserByEmail(users, email) {
30
+ return users.find(u => u.Email?.toLowerCase() === email.toLowerCase());
31
+ }
32
+ /** Discover a `{{UserID}}`-scoped RLS filter from the provider (for the token-based checks). */
33
+ export function discoverTokenFilter(provider) {
34
+ const filters = provider.RowLevelSecurityFilters ?? [];
35
+ return filters.find(f => f.FilterText?.includes('{{UserID}}'));
36
+ }
37
+ /** Discover the first non-exempt (user, entity) pair — a user with a non-empty Read clause. */
38
+ export function discoverLivePair(provider, users) {
39
+ for (const u of users) {
40
+ for (const e of provider.Entities) {
41
+ const clause = e.GetUserRowLevelSecurityWhereClause(u, EntityPermissionType.Read, '');
42
+ if (clause && clause.trim() !== '') {
43
+ return { User: u, EntityName: e.Name };
44
+ }
45
+ }
46
+ }
47
+ return undefined;
48
+ }
49
+ /**
50
+ * Discover the RLS fixture: a two-user divergent-clause pair PLUS the two independent
51
+ * single-user pieces (TokenFilter, LivePair). Pure (no singletons) so it is unit-testable
52
+ * with a mocked provider + synthetic users. The two-user discovery iterates entities,
53
+ * computes each user's clause once per entity (O(entities × users)), and returns the first
54
+ * entity where two distinct users get different non-empty clauses. TokenFilter/LivePair are
55
+ * attached to every returned fixture regardless of two-user usability, so the single-user
56
+ * checks (RLS1/RLS5) run even when the DB lacks two divergent users.
57
+ */
58
+ export function discoverRlsFixture(provider, users) {
59
+ const distinct = users.filter((u, i) => users.findIndex(o => UUIDsEqual(o.ID, u.ID)) === i);
60
+ const tokenFilter = discoverTokenFilter(provider);
61
+ const livePair = discoverLivePair(provider, distinct);
62
+ const seededScopedA = findUserByEmail(users, SEEDED_SCOPED_A_EMAIL);
63
+ const seededScopedB = findUserByEmail(users, SEEDED_SCOPED_B_EMAIL);
64
+ const seededNoGrant = findUserByEmail(users, SEEDED_NOGRANT_EMAIL);
65
+ const attach = (fx) => ({ ...fx, TokenFilter: tokenFilter, LivePair: livePair, SeededScopedA: seededScopedA, SeededScopedB: seededScopedB, SeededNoGrant: seededNoGrant });
66
+ if (distinct.length < 2) {
67
+ return attach({ UserA: users[0], UserB: users[0], EntityName: '', Usable: false, Reason: 'fewer than two distinct users in the user cache' });
68
+ }
69
+ for (const entity of provider.Entities) {
70
+ // Compute each user's effective Read clause once for this entity; keep non-empty ones.
71
+ const withClause = [];
72
+ for (const u of distinct) {
73
+ const clause = entity.GetUserRowLevelSecurityWhereClause(u, EntityPermissionType.Read, '');
74
+ if (clause && clause.trim() !== '') {
75
+ withClause.push({ user: u, clause });
76
+ }
77
+ }
78
+ if (withClause.length < 2) {
79
+ continue;
80
+ }
81
+ for (let i = 0; i < withClause.length; i++) {
82
+ for (let j = i + 1; j < withClause.length; j++) {
83
+ if (withClause[i].clause !== withClause[j].clause) {
84
+ return attach({ UserA: withClause[i].user, UserB: withClause[j].user, EntityName: entity.Name, Usable: true });
85
+ }
86
+ }
87
+ }
88
+ }
89
+ return attach({ UserA: distinct[0], UserB: distinct[1], EntityName: '', Usable: false, Reason: 'only RLS-exempt users (no entity yields two distinct non-empty clauses)' });
90
+ }
91
+ //# sourceMappingURL=rls-fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-fixture.js","sourceRoot":"","sources":["../src/rls-fixture.ts"],"names":[],"mappings":"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"}