@oneuptime/common 12.0.14 → 12.0.15
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/Models/DatabaseModels/GlobalOidc.ts +20 -0
- package/Models/DatabaseModels/GlobalSso.ts +20 -0
- package/Models/DatabaseModels/MarketingConversion.ts +19 -9
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
- package/Models/DatabaseModels/StatusPage.ts +7 -4
- package/Server/API/StatusPageAPI.ts +29 -4
- package/Server/EnvironmentConfig.ts +7 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/SlackAuthorization.ts +43 -6
- package/Server/Middleware/UserAuthorization.ts +236 -26
- package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
- package/Server/Services/GlobalOidcProjectService.ts +126 -1
- package/Server/Services/GlobalOidcService.ts +132 -0
- package/Server/Services/GlobalSsoProjectService.ts +126 -1
- package/Server/Services/GlobalSsoService.ts +132 -0
- package/Server/Services/UserEmailService.ts +4 -4
- package/Server/Utils/CronTab.ts +73 -0
- package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
- package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
- package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
- package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
- package/Server/Utils/Monitor/MonitorResource.ts +161 -21
- package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
- package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
- package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
- package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
- package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
- package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
- package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
- package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
- package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
- package/Tests/Server/Utils/CronTab.test.ts +116 -0
- package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
- package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
- package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
- package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
- package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
- package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
- package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
- package/Tests/Utils/Analytics.test.ts +187 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
- package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
- package/Types/Marketing/MarketingConversion.ts +12 -0
- package/Types/Monitor/CriteriaFilter.ts +9 -0
- package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
- package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
- package/UI/Components/Forms/BasicForm.tsx +13 -1
- package/UI/Components/Forms/BasicModelForm.tsx +2 -1
- package/UI/Components/Forms/ModelForm.tsx +10 -1
- package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
- package/Utils/Analytics.ts +55 -2
- package/Utils/Monitor/MonitorMetricType.ts +40 -0
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
- package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +25 -4
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/SlackAuthorization.js +28 -4
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcService.js +127 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoService.js +127 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +4 -4
- package/build/dist/Server/Utils/CronTab.js +66 -0
- package/build/dist/Server/Utils/CronTab.js.map +1 -1
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
- package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
- package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/Utils/Analytics.js +50 -2
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { describe, expect, jest, test } from "@jest/globals";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* neutralizeAssistantMarkdown parses with unified/remark and serializes with
|
|
5
|
+
* mdast-util-to-markdown. Those packages are ESM-only and this project's jest
|
|
6
|
+
* config does not transform them, so they cannot be imported under test. We
|
|
7
|
+
* therefore replace the parser and serializer with tiny stubs:
|
|
8
|
+
*
|
|
9
|
+
* - `unified().use(...).parse(input)` returns `JSON.parse(input)`, letting a
|
|
10
|
+
* test hand the function an exact mdast tree instead of a markdown string.
|
|
11
|
+
* Feeding it a non-JSON string makes JSON.parse throw, which exercises the
|
|
12
|
+
* function's own parse-failure fallback.
|
|
13
|
+
* - `toMarkdown(tree)` returns `JSON.stringify(tree)`, so the string the
|
|
14
|
+
* function returns is the neutralized tree itself and assertions can read
|
|
15
|
+
* the exact nodes the real logic (neutralizeNode / inertLink /
|
|
16
|
+
* collectDefinitions / the root re-wrap) produced.
|
|
17
|
+
*
|
|
18
|
+
* remark-gfm is already stubbed to a no-op by the jest moduleNameMapper.
|
|
19
|
+
*/
|
|
20
|
+
jest.mock("remark-parse", () => {
|
|
21
|
+
return {
|
|
22
|
+
__esModule: true,
|
|
23
|
+
default: (): void => {},
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
jest.mock("unified", () => {
|
|
28
|
+
interface MockChain {
|
|
29
|
+
use: () => MockChain;
|
|
30
|
+
parse: (input: string) => unknown;
|
|
31
|
+
}
|
|
32
|
+
const makeChain: () => MockChain = (): MockChain => {
|
|
33
|
+
const chain: MockChain = {
|
|
34
|
+
use: (): MockChain => {
|
|
35
|
+
return chain;
|
|
36
|
+
},
|
|
37
|
+
parse: (input: string): unknown => {
|
|
38
|
+
return JSON.parse(input);
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return chain;
|
|
42
|
+
};
|
|
43
|
+
return { __esModule: true, unified: makeChain };
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
jest.mock("mdast-util-to-markdown", () => {
|
|
47
|
+
return {
|
|
48
|
+
__esModule: true,
|
|
49
|
+
toMarkdown: (tree: unknown): string => {
|
|
50
|
+
return JSON.stringify(tree);
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
jest.mock("mdast-util-gfm", () => {
|
|
56
|
+
return {
|
|
57
|
+
__esModule: true,
|
|
58
|
+
gfmToMarkdown: (): Record<string, unknown> => {
|
|
59
|
+
return {};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
import neutralizeAssistantMarkdown from "../../../../UI/Utils/AIChatExport/MarkdownSafety";
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* A structural view of the mdast nodes the tests build and inspect. It mirrors
|
|
68
|
+
* the shape the function reads and writes; JSON round-tripping drops any key
|
|
69
|
+
* whose value is undefined, which is fine for these assertions.
|
|
70
|
+
*/
|
|
71
|
+
interface TreeNode {
|
|
72
|
+
type: string;
|
|
73
|
+
value?: string;
|
|
74
|
+
url?: string;
|
|
75
|
+
alt?: string | null;
|
|
76
|
+
identifier?: string;
|
|
77
|
+
depth?: number;
|
|
78
|
+
children?: Array<TreeNode>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* Serialize a tree in, parse the neutralized tree back out. The function's
|
|
83
|
+
* happy path returns JSON.stringify of the tree it built (see the module
|
|
84
|
+
* mocks), so this hands back the neutralized tree for structural assertions.
|
|
85
|
+
*/
|
|
86
|
+
function run(tree: TreeNode): TreeNode {
|
|
87
|
+
const output: string = neutralizeAssistantMarkdown(JSON.stringify(tree));
|
|
88
|
+
return JSON.parse(output) as TreeNode;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function root(children: Array<TreeNode>): TreeNode {
|
|
92
|
+
return { type: "root", children: children };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function paragraph(children: Array<TreeNode>): TreeNode {
|
|
96
|
+
return { type: "paragraph", children: children };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function text(value: string): TreeNode {
|
|
100
|
+
return { type: "text", value: value };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Every node type present anywhere in the tree, root first.
|
|
104
|
+
function collectTypes(node: TreeNode): Array<string> {
|
|
105
|
+
const types: Array<string> = [node.type];
|
|
106
|
+
for (const child of node.children || []) {
|
|
107
|
+
types.push(...collectTypes(child));
|
|
108
|
+
}
|
|
109
|
+
return types;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The value of every inlineCode node, in document order.
|
|
113
|
+
function collectInlineCode(node: TreeNode): Array<string> {
|
|
114
|
+
const values: Array<string> = [];
|
|
115
|
+
if (node.type === "inlineCode" && node.value !== undefined) {
|
|
116
|
+
values.push(node.value);
|
|
117
|
+
}
|
|
118
|
+
for (const child of node.children || []) {
|
|
119
|
+
values.push(...collectInlineCode(child));
|
|
120
|
+
}
|
|
121
|
+
return values;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// The value of every text node, in document order.
|
|
125
|
+
function collectText(node: TreeNode): Array<string> {
|
|
126
|
+
const values: Array<string> = [];
|
|
127
|
+
if (node.type === "text" && node.value !== undefined) {
|
|
128
|
+
values.push(node.value);
|
|
129
|
+
}
|
|
130
|
+
for (const child of node.children || []) {
|
|
131
|
+
values.push(...collectText(child));
|
|
132
|
+
}
|
|
133
|
+
return values;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
describe("neutralizeAssistantMarkdown", () => {
|
|
137
|
+
describe("empty input and passthrough", () => {
|
|
138
|
+
test("returns an empty string for empty input without parsing", () => {
|
|
139
|
+
expect(neutralizeAssistantMarkdown("")).toBe("");
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
test("leaves ordinary text untouched", () => {
|
|
143
|
+
const result: TreeNode = run(root([paragraph([text("hello world")])]));
|
|
144
|
+
|
|
145
|
+
expect(collectTypes(result)).toEqual(["root", "paragraph", "text"]);
|
|
146
|
+
expect(collectText(result)).toEqual(["hello world"]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("leaves an inline code span untouched", () => {
|
|
150
|
+
const result: TreeNode = run(
|
|
151
|
+
root([paragraph([{ type: "inlineCode", value: "curl x" }])]),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
expect(collectInlineCode(result)).toEqual(["curl x"]);
|
|
155
|
+
expect(collectTypes(result)).not.toContain("link");
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("links", () => {
|
|
160
|
+
test("keeps a distinct label and demotes the url to an inert code span", () => {
|
|
161
|
+
const result: TreeNode = run(
|
|
162
|
+
root([
|
|
163
|
+
paragraph([
|
|
164
|
+
{
|
|
165
|
+
type: "link",
|
|
166
|
+
url: "https://evil.example/steal",
|
|
167
|
+
children: [text("click here")],
|
|
168
|
+
},
|
|
169
|
+
]),
|
|
170
|
+
]),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(collectTypes(result)).not.toContain("link");
|
|
174
|
+
expect(collectInlineCode(result)).toEqual(["https://evil.example/steal"]);
|
|
175
|
+
// "label (`url`)" — the label survives, then " (", the code span, ")".
|
|
176
|
+
expect(collectText(result)).toEqual(["click here", " (", ")"]);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("a url-only label (autolink) collapses to a single code span", () => {
|
|
180
|
+
const result: TreeNode = run(
|
|
181
|
+
root([
|
|
182
|
+
paragraph([
|
|
183
|
+
{
|
|
184
|
+
type: "link",
|
|
185
|
+
url: "https://evil.example",
|
|
186
|
+
children: [text("https://evil.example")],
|
|
187
|
+
},
|
|
188
|
+
]),
|
|
189
|
+
]),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
// No repeated label, no wrapping parens — just the inert url.
|
|
193
|
+
expect(collectInlineCode(result)).toEqual(["https://evil.example"]);
|
|
194
|
+
expect(collectText(result)).toEqual([]);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("a link with no url keeps its distinct label as plain text", () => {
|
|
198
|
+
const result: TreeNode = run(
|
|
199
|
+
root([paragraph([{ type: "link", url: "", children: [text("see")] }])]),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(collectInlineCode(result)).toEqual([]);
|
|
203
|
+
expect(collectText(result)).toEqual(["see"]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("a link with neither url nor label collapses to a single empty text node", () => {
|
|
207
|
+
const result: TreeNode = run(
|
|
208
|
+
root([paragraph([{ type: "link", url: "", children: [] }])]),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const para: TreeNode = (result.children || [])[0] as TreeNode;
|
|
212
|
+
expect(para.children).toEqual([{ type: "text", value: "" }]);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("neutralizes every link in a paragraph and keeps their order", () => {
|
|
216
|
+
const result: TreeNode = run(
|
|
217
|
+
root([
|
|
218
|
+
paragraph([
|
|
219
|
+
{
|
|
220
|
+
type: "link",
|
|
221
|
+
url: "https://evil.example/a",
|
|
222
|
+
children: [text("first")],
|
|
223
|
+
},
|
|
224
|
+
text(" and "),
|
|
225
|
+
{
|
|
226
|
+
type: "link",
|
|
227
|
+
url: "https://evil.example/b",
|
|
228
|
+
children: [text("second")],
|
|
229
|
+
},
|
|
230
|
+
]),
|
|
231
|
+
]),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(collectInlineCode(result)).toEqual([
|
|
235
|
+
"https://evil.example/a",
|
|
236
|
+
"https://evil.example/b",
|
|
237
|
+
]);
|
|
238
|
+
expect(collectText(result)).toEqual([
|
|
239
|
+
"first",
|
|
240
|
+
" (",
|
|
241
|
+
")",
|
|
242
|
+
" and ",
|
|
243
|
+
"second",
|
|
244
|
+
" (",
|
|
245
|
+
")",
|
|
246
|
+
]);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("preserves a rich label subtree while still neutralizing the url", () => {
|
|
250
|
+
const result: TreeNode = run(
|
|
251
|
+
root([
|
|
252
|
+
paragraph([
|
|
253
|
+
{
|
|
254
|
+
type: "link",
|
|
255
|
+
url: "https://evil.example",
|
|
256
|
+
children: [
|
|
257
|
+
{ type: "emphasis", children: [text("bold")] },
|
|
258
|
+
text(" label"),
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
]),
|
|
262
|
+
]),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
/*
|
|
266
|
+
* The distinct-label check reads the whole subtree via textOf, so the
|
|
267
|
+
* emphasis node survives and the url is the only code span.
|
|
268
|
+
*/
|
|
269
|
+
expect(collectTypes(result)).toContain("emphasis");
|
|
270
|
+
expect(collectTypes(result)).not.toContain("link");
|
|
271
|
+
expect(collectInlineCode(result)).toEqual(["https://evil.example"]);
|
|
272
|
+
expect(collectText(result)).toContain("bold");
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("images", () => {
|
|
277
|
+
test("rewrites an image into an [image: alt] label and an inert url", () => {
|
|
278
|
+
const result: TreeNode = run(
|
|
279
|
+
root([
|
|
280
|
+
paragraph([
|
|
281
|
+
{
|
|
282
|
+
type: "image",
|
|
283
|
+
url: "https://evil.example/pixel.png",
|
|
284
|
+
alt: "logo",
|
|
285
|
+
},
|
|
286
|
+
]),
|
|
287
|
+
]),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
expect(collectTypes(result)).not.toContain("image");
|
|
291
|
+
expect(collectInlineCode(result)).toEqual([
|
|
292
|
+
"https://evil.example/pixel.png",
|
|
293
|
+
]);
|
|
294
|
+
expect(collectText(result)).toContain("[image: logo]");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("uses an empty alt slot when the image has no alt", () => {
|
|
298
|
+
const result: TreeNode = run(
|
|
299
|
+
root([
|
|
300
|
+
paragraph([{ type: "image", url: "https://evil.example/x.png" }]),
|
|
301
|
+
]),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
expect(collectText(result)).toContain("[image: ]");
|
|
305
|
+
expect(collectInlineCode(result)).toEqual(["https://evil.example/x.png"]);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test("an image with no url keeps only its [image: alt] label", () => {
|
|
309
|
+
const result: TreeNode = run(
|
|
310
|
+
root([paragraph([{ type: "image", url: "", alt: "logo" }])]),
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
expect(collectInlineCode(result)).toEqual([]);
|
|
314
|
+
expect(collectText(result)).toEqual(["[image: logo]"]);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe("reference links, images and definitions", () => {
|
|
319
|
+
test("resolves a link reference through its definition and drops the definition", () => {
|
|
320
|
+
const result: TreeNode = run(
|
|
321
|
+
root([
|
|
322
|
+
paragraph([
|
|
323
|
+
{
|
|
324
|
+
type: "linkReference",
|
|
325
|
+
identifier: "ref",
|
|
326
|
+
children: [text("click here")],
|
|
327
|
+
},
|
|
328
|
+
]),
|
|
329
|
+
{
|
|
330
|
+
type: "definition",
|
|
331
|
+
identifier: "ref",
|
|
332
|
+
url: "https://evil.example/ref",
|
|
333
|
+
},
|
|
334
|
+
]),
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// The definition node is gone, so "[x][ref]" can no longer resolve live.
|
|
338
|
+
expect(collectTypes(result)).not.toContain("definition");
|
|
339
|
+
expect(collectTypes(result)).not.toContain("linkReference");
|
|
340
|
+
expect(result.children).toHaveLength(1);
|
|
341
|
+
expect(collectInlineCode(result)).toEqual(["https://evil.example/ref"]);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("matches a reference to its definition case-insensitively", () => {
|
|
345
|
+
const result: TreeNode = run(
|
|
346
|
+
root([
|
|
347
|
+
paragraph([
|
|
348
|
+
{
|
|
349
|
+
type: "linkReference",
|
|
350
|
+
identifier: "REF-One",
|
|
351
|
+
children: [text("x")],
|
|
352
|
+
},
|
|
353
|
+
]),
|
|
354
|
+
{
|
|
355
|
+
type: "definition",
|
|
356
|
+
identifier: "ref-one",
|
|
357
|
+
url: "https://evil.example/case",
|
|
358
|
+
},
|
|
359
|
+
]),
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
expect(collectInlineCode(result)).toEqual(["https://evil.example/case"]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("resolves an image reference through its definition", () => {
|
|
366
|
+
const result: TreeNode = run(
|
|
367
|
+
root([
|
|
368
|
+
paragraph([
|
|
369
|
+
{ type: "imageReference", identifier: "img", alt: "logo" },
|
|
370
|
+
]),
|
|
371
|
+
{
|
|
372
|
+
type: "definition",
|
|
373
|
+
identifier: "img",
|
|
374
|
+
url: "https://evil.example/logo.png",
|
|
375
|
+
},
|
|
376
|
+
]),
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
expect(collectTypes(result)).not.toContain("imageReference");
|
|
380
|
+
expect(collectText(result)).toContain("[image: logo]");
|
|
381
|
+
expect(collectInlineCode(result)).toEqual([
|
|
382
|
+
"https://evil.example/logo.png",
|
|
383
|
+
]);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("degrades a reference with no matching definition to its label text", () => {
|
|
387
|
+
const result: TreeNode = run(
|
|
388
|
+
root([
|
|
389
|
+
paragraph([
|
|
390
|
+
{
|
|
391
|
+
type: "linkReference",
|
|
392
|
+
identifier: "missing",
|
|
393
|
+
children: [text("foo")],
|
|
394
|
+
},
|
|
395
|
+
]),
|
|
396
|
+
]),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
// No definition means an empty url, so only the plain label remains.
|
|
400
|
+
expect(collectInlineCode(result)).toEqual([]);
|
|
401
|
+
expect(collectText(result)).toEqual(["foo"]);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("a document that is only a definition serializes to an empty tree", () => {
|
|
405
|
+
const result: TreeNode = run(
|
|
406
|
+
root([
|
|
407
|
+
{
|
|
408
|
+
type: "definition",
|
|
409
|
+
identifier: "ref",
|
|
410
|
+
url: "https://evil.example",
|
|
411
|
+
},
|
|
412
|
+
]),
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
expect(result.children).toEqual([]);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe("raw HTML", () => {
|
|
420
|
+
test("demotes a root-level html node to a paragraph of inert text", () => {
|
|
421
|
+
const result: TreeNode = run(
|
|
422
|
+
root([
|
|
423
|
+
{ type: "html", value: '<img src="https://evil.example/pixel.png">' },
|
|
424
|
+
]),
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
expect(collectTypes(result)).toEqual(["root", "paragraph", "text"]);
|
|
428
|
+
expect(collectText(result)).toEqual([
|
|
429
|
+
'<img src="https://evil.example/pixel.png">',
|
|
430
|
+
]);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("demotes an inline html node to a text node in place", () => {
|
|
434
|
+
const result: TreeNode = run(
|
|
435
|
+
root([
|
|
436
|
+
paragraph([
|
|
437
|
+
text("before "),
|
|
438
|
+
{ type: "html", value: "<b>" },
|
|
439
|
+
text(" after"),
|
|
440
|
+
]),
|
|
441
|
+
]),
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
expect(collectTypes(result)).not.toContain("html");
|
|
445
|
+
expect(collectText(result)).toEqual(["before ", "<b>", " after"]);
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
describe("tree shape and precedence", () => {
|
|
450
|
+
test("rewrites a link nested in emphasis depth-first and keeps the emphasis", () => {
|
|
451
|
+
const result: TreeNode = run(
|
|
452
|
+
root([
|
|
453
|
+
paragraph([
|
|
454
|
+
{
|
|
455
|
+
type: "emphasis",
|
|
456
|
+
children: [
|
|
457
|
+
{
|
|
458
|
+
type: "link",
|
|
459
|
+
url: "https://evil.example",
|
|
460
|
+
children: [text("click")],
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
},
|
|
464
|
+
]),
|
|
465
|
+
]),
|
|
466
|
+
);
|
|
467
|
+
|
|
468
|
+
const para: TreeNode = (result.children || [])[0] as TreeNode;
|
|
469
|
+
const emphasis: TreeNode = (para.children || [])[0] as TreeNode;
|
|
470
|
+
expect(emphasis.type).toBe("emphasis");
|
|
471
|
+
expect(collectTypes(emphasis)).not.toContain("link");
|
|
472
|
+
expect(collectInlineCode(emphasis)).toEqual(["https://evil.example"]);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test("neutralizes a link inside a heading and preserves the heading", () => {
|
|
476
|
+
const result: TreeNode = run(
|
|
477
|
+
root([
|
|
478
|
+
{
|
|
479
|
+
type: "heading",
|
|
480
|
+
depth: 2,
|
|
481
|
+
children: [
|
|
482
|
+
text("See "),
|
|
483
|
+
{
|
|
484
|
+
type: "link",
|
|
485
|
+
url: "https://evil.example",
|
|
486
|
+
children: [text("here")],
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
},
|
|
490
|
+
]),
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
const heading: TreeNode = (result.children || [])[0] as TreeNode;
|
|
494
|
+
expect(heading.type).toBe("heading");
|
|
495
|
+
expect(heading.depth).toBe(2);
|
|
496
|
+
expect(collectTypes(result)).not.toContain("link");
|
|
497
|
+
expect(collectInlineCode(result)).toEqual(["https://evil.example"]);
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
test("re-wraps a bare phrasing node left at the root into a paragraph", () => {
|
|
501
|
+
const result: TreeNode = run(root([text("loose")]));
|
|
502
|
+
|
|
503
|
+
expect(result.children).toEqual([
|
|
504
|
+
{ type: "paragraph", children: [{ type: "text", value: "loose" }] },
|
|
505
|
+
]);
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
test("re-wraps a bare inline code node left at the root into a paragraph", () => {
|
|
509
|
+
const result: TreeNode = run(root([{ type: "inlineCode", value: "x" }]));
|
|
510
|
+
|
|
511
|
+
const child: TreeNode = (result.children || [])[0] as TreeNode;
|
|
512
|
+
expect(child.type).toBe("paragraph");
|
|
513
|
+
expect(child.children).toEqual([{ type: "inlineCode", value: "x" }]);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
test("leaves a block node at the root unwrapped", () => {
|
|
517
|
+
const result: TreeNode = run(
|
|
518
|
+
root([{ type: "code", value: "console.log(1)" }]),
|
|
519
|
+
);
|
|
520
|
+
|
|
521
|
+
const child: TreeNode = (result.children || [])[0] as TreeNode;
|
|
522
|
+
expect(child.type).toBe("code");
|
|
523
|
+
expect(child.value).toBe("console.log(1)");
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
describe("parse-failure fallback", () => {
|
|
528
|
+
test("degrades to a fenced code block rather than emitting live markup", () => {
|
|
529
|
+
/*
|
|
530
|
+
* A non-JSON string makes the stubbed parser throw, standing in for any
|
|
531
|
+
* real parse failure.
|
|
532
|
+
*/
|
|
533
|
+
expect(neutralizeAssistantMarkdown("not valid json")).toBe(
|
|
534
|
+
"```\nnot valid json\n```",
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test("neutralizes triple backticks in the fallback so they cannot break the fence", () => {
|
|
539
|
+
expect(neutralizeAssistantMarkdown("```danger```")).toBe(
|
|
540
|
+
"```\n'''danger'''\n```",
|
|
541
|
+
);
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
});
|