Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions osv-scanner.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ reason = "dev-only: bundled inside the npm CLI that @semantic-release/npm runs a
id = "GHSA-rj75-hqrm-r3gf" # postcss-selector-parser 7.1.4 (npm bundle)
ignoreUntil = 2026-11-03
reason = "dev-only: bundled inside the npm CLI that @semantic-release/npm runs at release time; the latest npm 11 (11.21.0) still bundles 7.1.4 and overrides cannot change bundled packages. Not part of the published package."

[[IgnoredVulns]]
id = "GHSA-rxp7-6hxx-x8q5"
ignoreUntil = 2027-01-04T00:00:00Z
reason = "ip-address 10.5.0 is bundled inside the npm package (node_modules/npm, inBundle) that the release tooling (semantic-release) installs; npm overrides cannot reach bundled files and the latest npm still bundles this version. Dev-only, never shipped in @norbix.ai/react-redux. Same decision as sdk-ts 96936c5."
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"prepublishOnly": "npm run build"
},
"peerDependencies": {
"@norbix.ai/ts": ">=4.32.0",
"@norbix.ai/ts": ">=4.37.0",
"@reduxjs/toolkit": ">=2.0.0",
"react": ">=18.0.0",
"react-redux": ">=9.0.0"
Expand All @@ -68,7 +68,7 @@
"@commitlint/cli": "^21.2.2",
"@commitlint/config-conventional": "^21.2.2",
"@eslint/js": "^10.0.1",
"@norbix.ai/ts": "^4.32.0",
"@norbix.ai/ts": "^4.37.0",
"@reduxjs/toolkit": "^2.12.0",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/github": "^12.0.9",
Expand Down
82 changes: 82 additions & 0 deletions tests/template-clone.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { Norbix } from '@norbix.ai/ts';
import type { CodeMashHub2 } from '@norbix.ai/ts/types/hub';
import { describe, expect, expectTypeOf, it, vi } from 'vitest';

import { createNorbixBaseQuery } from '../src/baseQuery.js';
import { hubNotifications } from '../src/hooks/hub/notifications.js';
import type { Result } from '../src/hooks/types.js';

/**
* Template clone answers with the new copy's id (gateway 2026-10-23,
* @norbix.ai/ts 4.37.0: `IdResponse`, was `EmptyResponse`). The clone hooks
* hand that answer to the caller in `data`, so the dashboard can open the copy
* right away. Driven through the real `@norbix.ai/ts` client and this
* package's base query with a fake fetch — never a real server.
*/
type Def = { query: (args: unknown) => (norbix: unknown) => unknown };

function fakeBuilder() {
return {
query: (def: Record<string, unknown>) => def,
mutation: (def: Record<string, unknown>) => def,
} as never;
}

const endpoints = hubNotifications(fakeBuilder()) as unknown as Record<string, Def>;

type Notifications = Norbix['hub']['notifications'];

function run(key: string, args: unknown, answer: unknown) {
type FetchFn = typeof globalThis.fetch;
const seen: Array<{ url: string; method: string }> = [];
const fetchImpl = vi.fn(async (...[input, init]: Parameters<FetchFn>) => {
seen.push({ url: String(input), method: (init?.method ?? 'GET').toUpperCase() });
return new globalThis.Response(JSON.stringify(answer), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}) as unknown as FetchFn;
const norbix = new Norbix({
bearerToken: 'test-token',
projectId: 'test-project',
hubVersion: 'v2',
baseUrl: { api: 'https://api.norbix.io', hub: 'https://hub.norbix.io' },
fetch: fetchImpl,
});
const baseQuery = createNorbixBaseQuery(() => norbix);
const result = baseQuery(endpoints[key]!.query(args) as never, {} as never, {});
return { seen, result };
}

describe('template clone hooks return the new copy id', () => {
it('the clone hooks are typed with the id answer', () => {
expectTypeOf<
Result<Notifications['cloneEmailTemplate']>
>().toEqualTypeOf<CodeMashHub2.IdResponse>();
expectTypeOf<
Result<Notifications['cloneSmsTemplate']>
>().toEqualTypeOf<CodeMashHub2.IdResponse>();
expectTypeOf<
Result<Notifications['clonePushTemplate']>
>().toEqualTypeOf<CodeMashHub2.IdResponse>();
});

it.each([
['cloneEmailTemplate', 'email'],
['cloneSmsTemplate', 'sms'],
['clonePushTemplate', 'push'],
])('%s puts the copy id in data', async (key, channel) => {
const { seen, result } = run(key, { id: 'tpl_1' }, { id: `${channel}_copy` });

const out = (await result) as { data?: CodeMashHub2.IdResponse; error?: unknown };

expect(out.error).toBeUndefined();
expect(seen).toEqual([
{
method: 'POST',
url: `https://hub.norbix.io/v2/notifications/${channel}/templates/tpl_1/clone`,
},
]);
expect(out.data?.id).toBe(`${channel}_copy`);
});
});