ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Рефакторинг этапов 2.50: вынос resolveSessionOrganizationScopeContext(...) + related scope sanitation в отдельный runtime-adapter, чтобы добить верхний слой assistantService.
This commit is contained in:
parent
ca467cdecc
commit
5e4cc0ed67
|
|
@ -1572,7 +1572,45 @@ Validation:
|
||||||
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
||||||
- `assistantWave10SettlementCorrectiveRegression.test.ts`
|
- `assistantWave10SettlementCorrectiveRegression.test.ts`
|
||||||
|
|
||||||
Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 + 2.49 completed)**
|
Implemented in current pass (Phase 2.50):
|
||||||
|
1. Extracted organization-scope runtime logic from `assistantService` into dedicated adapter:
|
||||||
|
- `assistantOrganizationScopeRuntimeAdapter.ts`
|
||||||
|
- introduced:
|
||||||
|
- `resolveSessionOrganizationScopeContextRuntime(...)`
|
||||||
|
- `mergeFollowupContextWithOrganizationScopeRuntime(...)`
|
||||||
|
2. Rewired `assistantService` scope helpers to delegate through the adapter (behavior-preserving):
|
||||||
|
- `resolveSessionOrganizationScopeContext(...)` now uses runtime adapter with existing extraction/scoring/sanitization helpers;
|
||||||
|
- `mergeFollowupContextWithOrganizationScope(...)` now uses runtime adapter while preserving existing normalization/toNonEmpty semantics.
|
||||||
|
3. Added focused unit tests:
|
||||||
|
- `assistantOrganizationScopeRuntimeAdapter.test.ts`
|
||||||
|
|
||||||
|
Validation:
|
||||||
|
1. `npm run build` passed.
|
||||||
|
2. Targeted living/address/deep followup pack passed:
|
||||||
|
- `assistantOrganizationScopeRuntimeAdapter.test.ts`
|
||||||
|
- `assistantTurnRuntimeDepsAdapter.test.ts`
|
||||||
|
- `assistantTurnRuntimeInputBuilder.test.ts`
|
||||||
|
- `assistantTurnAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantAddressAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantDeepTurnAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantDeepTurnResponseAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantDeepTurnAnalysisAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantDeepTurnAnalysisRuntimeAdapter.test.ts`
|
||||||
|
- `assistantAddressLaneResponseAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantLivingChatAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantAddressLaneAttemptRuntimeAdapter.test.ts`
|
||||||
|
- `assistantUserTurnBootstrapRuntimeAdapter.test.ts`
|
||||||
|
- `assistantLivingChatLlmRuntimeAdapter.test.ts`
|
||||||
|
- `assistantLivingChatHandlerRuntimeAdapter.test.ts`
|
||||||
|
- `assistantLivingChatRuntimeAdapter.test.ts`
|
||||||
|
- `assistantAddressRuntimeAdapter.test.ts`
|
||||||
|
- `assistantAddressLaneResponseRuntimeAdapter.test.ts`
|
||||||
|
- `assistantDeepTurnResponseRuntimeAdapter.test.ts`
|
||||||
|
- `assistantDeepTurnPackagingRuntimeAdapter.test.ts`
|
||||||
|
- `assistantWave10SettlementCorrectiveRegression.test.ts`
|
||||||
|
- `assistantLivingChatMode.test.ts`
|
||||||
|
|
||||||
|
Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 + 2.24 + 2.25 + 2.26 + 2.27 + 2.28 + 2.29 + 2.30 + 2.31 + 2.32 + 2.33 + 2.34 + 2.35 + 2.36 + 2.37 + 2.38 + 2.39 + 2.40 + 2.41 + 2.42 + 2.43 + 2.44 + 2.45 + 2.46 + 2.47 + 2.48 + 2.49 + 2.50 completed)**
|
||||||
|
|
||||||
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards)
|
||||||
|
|
||||||
|
|
|
||||||
32
llm_normalizer/backend/dist/services/assistantOrganizationScopeRuntimeAdapter.js
vendored
Normal file
32
llm_normalizer/backend/dist/services/assistantOrganizationScopeRuntimeAdapter.js
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.resolveSessionOrganizationScopeContextRuntime = resolveSessionOrganizationScopeContextRuntime;
|
||||||
|
exports.mergeFollowupContextWithOrganizationScopeRuntime = mergeFollowupContextWithOrganizationScopeRuntime;
|
||||||
|
function resolveSessionOrganizationScopeContextRuntime(input) {
|
||||||
|
const knownOrganizations = input.extractKnownOrganizationsFromHistory(input.items);
|
||||||
|
const selectedOrganization = input.resolveOrganizationSelectionFromMessage(input.userMessage, knownOrganizations);
|
||||||
|
const lastActiveOrganization = input.findLastAssistantActiveOrganization(input.items);
|
||||||
|
const activeOrganization = selectedOrganization ?? input.normalizeOrganizationScopeValue(lastActiveOrganization);
|
||||||
|
return {
|
||||||
|
knownOrganizations,
|
||||||
|
selectedOrganization,
|
||||||
|
activeOrganization
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function mergeFollowupContextWithOrganizationScopeRuntime(input) {
|
||||||
|
const normalizedOrganization = input.normalizeOrganizationScopeValue(input.organization);
|
||||||
|
const hasBase = input.followupContext && typeof input.followupContext === "object";
|
||||||
|
const base = hasBase ? { ...input.followupContext } : {};
|
||||||
|
if (!normalizedOrganization) {
|
||||||
|
return hasBase ? base : null;
|
||||||
|
}
|
||||||
|
const previousFiltersRaw = base.previous_filters;
|
||||||
|
const previousFilters = previousFiltersRaw && typeof previousFiltersRaw === "object"
|
||||||
|
? { ...previousFiltersRaw }
|
||||||
|
: {};
|
||||||
|
if (!input.toNonEmptyString(previousFilters.organization)) {
|
||||||
|
previousFilters.organization = normalizedOrganization;
|
||||||
|
}
|
||||||
|
base.previous_filters = previousFilters;
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
@ -65,6 +65,7 @@ const assistantCanon_1 = __importStar(require("./assistantCanon"));
|
||||||
const assistantAddressAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressAttemptRuntimeAdapter"));
|
const assistantAddressAttemptRuntimeAdapter_1 = __importStar(require("./assistantAddressAttemptRuntimeAdapter"));
|
||||||
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
|
const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding"));
|
||||||
const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter"));
|
const assistantDeepTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnAttemptRuntimeAdapter"));
|
||||||
|
const assistantOrganizationScopeRuntimeAdapter_1 = __importStar(require("./assistantOrganizationScopeRuntimeAdapter"));
|
||||||
const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter"));
|
const assistantTurnAttemptRuntimeAdapter_1 = __importStar(require("./assistantTurnAttemptRuntimeAdapter"));
|
||||||
const assistantTurnRuntimeDepsAdapter_1 = __importStar(require("./assistantTurnRuntimeDepsAdapter"));
|
const assistantTurnRuntimeDepsAdapter_1 = __importStar(require("./assistantTurnRuntimeDepsAdapter"));
|
||||||
const assistantTurnRuntimeInputBuilder_1 = __importStar(require("./assistantTurnRuntimeInputBuilder"));
|
const assistantTurnRuntimeInputBuilder_1 = __importStar(require("./assistantTurnRuntimeInputBuilder"));
|
||||||
|
|
@ -3772,30 +3773,22 @@ function resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations
|
||||||
return best.organization;
|
return best.organization;
|
||||||
}
|
}
|
||||||
function resolveSessionOrganizationScopeContext(userMessage, items) {
|
function resolveSessionOrganizationScopeContext(userMessage, items) {
|
||||||
const knownOrganizations = extractKnownOrganizationsFromHistory(items);
|
return (0, assistantOrganizationScopeRuntimeAdapter_1.resolveSessionOrganizationScopeContextRuntime)({
|
||||||
const selectedOrganization = resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations);
|
userMessage,
|
||||||
const lastActiveOrganization = findLastAssistantActiveOrganization(items);
|
items,
|
||||||
const activeOrganization = selectedOrganization ?? normalizeOrganizationScopeValue(lastActiveOrganization);
|
extractKnownOrganizationsFromHistory,
|
||||||
return {
|
resolveOrganizationSelectionFromMessage,
|
||||||
knownOrganizations,
|
findLastAssistantActiveOrganization,
|
||||||
selectedOrganization,
|
normalizeOrganizationScopeValue
|
||||||
activeOrganization
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
function mergeFollowupContextWithOrganizationScope(followupContext, organization) {
|
function mergeFollowupContextWithOrganizationScope(followupContext, organization) {
|
||||||
const normalizedOrganization = normalizeOrganizationScopeValue(organization);
|
return (0, assistantOrganizationScopeRuntimeAdapter_1.mergeFollowupContextWithOrganizationScopeRuntime)({
|
||||||
const base = followupContext && typeof followupContext === "object" ? { ...followupContext } : {};
|
followupContext,
|
||||||
if (!normalizedOrganization) {
|
organization,
|
||||||
return followupContext && typeof followupContext === "object" ? base : null;
|
normalizeOrganizationScopeValue,
|
||||||
}
|
toNonEmptyString
|
||||||
const previousFilters = base.previous_filters && typeof base.previous_filters === "object"
|
});
|
||||||
? { ...base.previous_filters }
|
|
||||||
: {};
|
|
||||||
if (!toNonEmptyString(previousFilters.organization)) {
|
|
||||||
previousFilters.organization = normalizedOrganization;
|
|
||||||
}
|
|
||||||
base.previous_filters = previousFilters;
|
|
||||||
return base;
|
|
||||||
}
|
}
|
||||||
function resolveSessionOrganizationScopeContextForTests(userMessage, items) {
|
function resolveSessionOrganizationScopeContextForTests(userMessage, items) {
|
||||||
return resolveSessionOrganizationScopeContext(userMessage, items);
|
return resolveSessionOrganizationScopeContext(userMessage, items);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
export interface AssistantSessionOrganizationScopeContext {
|
||||||
|
knownOrganizations: string[];
|
||||||
|
selectedOrganization: string | null;
|
||||||
|
activeOrganization: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResolveSessionOrganizationScopeContextRuntimeInput<ItemType = unknown> {
|
||||||
|
userMessage: string;
|
||||||
|
items: ItemType[];
|
||||||
|
extractKnownOrganizationsFromHistory: (items: ItemType[]) => string[];
|
||||||
|
resolveOrganizationSelectionFromMessage: (userMessage: string, knownOrganizations: string[]) => string | null;
|
||||||
|
findLastAssistantActiveOrganization: (items: ItemType[]) => string | null;
|
||||||
|
normalizeOrganizationScopeValue: (value: unknown) => string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MergeFollowupContextWithOrganizationScopeRuntimeInput {
|
||||||
|
followupContext: unknown;
|
||||||
|
organization: unknown;
|
||||||
|
normalizeOrganizationScopeValue: (value: unknown) => string | null;
|
||||||
|
toNonEmptyString: (value: unknown) => string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveSessionOrganizationScopeContextRuntime<ItemType = unknown>(
|
||||||
|
input: ResolveSessionOrganizationScopeContextRuntimeInput<ItemType>
|
||||||
|
): AssistantSessionOrganizationScopeContext {
|
||||||
|
const knownOrganizations = input.extractKnownOrganizationsFromHistory(input.items);
|
||||||
|
const selectedOrganization = input.resolveOrganizationSelectionFromMessage(
|
||||||
|
input.userMessage,
|
||||||
|
knownOrganizations
|
||||||
|
);
|
||||||
|
const lastActiveOrganization = input.findLastAssistantActiveOrganization(input.items);
|
||||||
|
const activeOrganization = selectedOrganization ?? input.normalizeOrganizationScopeValue(lastActiveOrganization);
|
||||||
|
|
||||||
|
return {
|
||||||
|
knownOrganizations,
|
||||||
|
selectedOrganization,
|
||||||
|
activeOrganization
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mergeFollowupContextWithOrganizationScopeRuntime(
|
||||||
|
input: MergeFollowupContextWithOrganizationScopeRuntimeInput
|
||||||
|
): Record<string, unknown> | null {
|
||||||
|
const normalizedOrganization = input.normalizeOrganizationScopeValue(input.organization);
|
||||||
|
const hasBase = input.followupContext && typeof input.followupContext === "object";
|
||||||
|
const base = hasBase ? { ...(input.followupContext as Record<string, unknown>) } : {};
|
||||||
|
if (!normalizedOrganization) {
|
||||||
|
return hasBase ? base : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const previousFiltersRaw = base.previous_filters;
|
||||||
|
const previousFilters =
|
||||||
|
previousFiltersRaw && typeof previousFiltersRaw === "object"
|
||||||
|
? { ...(previousFiltersRaw as Record<string, unknown>) }
|
||||||
|
: {};
|
||||||
|
if (!input.toNonEmptyString(previousFilters.organization)) {
|
||||||
|
previousFilters.organization = normalizedOrganization;
|
||||||
|
}
|
||||||
|
base.previous_filters = previousFilters;
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ import * as assistantCanon_1 from "./assistantCanon";
|
||||||
import * as assistantAddressAttemptRuntimeAdapter_1 from "./assistantAddressAttemptRuntimeAdapter";
|
import * as assistantAddressAttemptRuntimeAdapter_1 from "./assistantAddressAttemptRuntimeAdapter";
|
||||||
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
|
import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding";
|
||||||
import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter";
|
import * as assistantDeepTurnAttemptRuntimeAdapter_1 from "./assistantDeepTurnAttemptRuntimeAdapter";
|
||||||
|
import * as assistantOrganizationScopeRuntimeAdapter_1 from "./assistantOrganizationScopeRuntimeAdapter";
|
||||||
import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter";
|
import * as assistantTurnAttemptRuntimeAdapter_1 from "./assistantTurnAttemptRuntimeAdapter";
|
||||||
import * as assistantTurnRuntimeDepsAdapter_1 from "./assistantTurnRuntimeDepsAdapter";
|
import * as assistantTurnRuntimeDepsAdapter_1 from "./assistantTurnRuntimeDepsAdapter";
|
||||||
import * as assistantTurnRuntimeInputBuilder_1 from "./assistantTurnRuntimeInputBuilder";
|
import * as assistantTurnRuntimeInputBuilder_1 from "./assistantTurnRuntimeInputBuilder";
|
||||||
|
|
@ -3727,30 +3728,22 @@ function resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations
|
||||||
return best.organization;
|
return best.organization;
|
||||||
}
|
}
|
||||||
function resolveSessionOrganizationScopeContext(userMessage, items) {
|
function resolveSessionOrganizationScopeContext(userMessage, items) {
|
||||||
const knownOrganizations = extractKnownOrganizationsFromHistory(items);
|
return (0, assistantOrganizationScopeRuntimeAdapter_1.resolveSessionOrganizationScopeContextRuntime)({
|
||||||
const selectedOrganization = resolveOrganizationSelectionFromMessage(userMessage, knownOrganizations);
|
userMessage,
|
||||||
const lastActiveOrganization = findLastAssistantActiveOrganization(items);
|
items,
|
||||||
const activeOrganization = selectedOrganization ?? normalizeOrganizationScopeValue(lastActiveOrganization);
|
extractKnownOrganizationsFromHistory,
|
||||||
return {
|
resolveOrganizationSelectionFromMessage,
|
||||||
knownOrganizations,
|
findLastAssistantActiveOrganization,
|
||||||
selectedOrganization,
|
normalizeOrganizationScopeValue
|
||||||
activeOrganization
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
function mergeFollowupContextWithOrganizationScope(followupContext, organization) {
|
function mergeFollowupContextWithOrganizationScope(followupContext, organization) {
|
||||||
const normalizedOrganization = normalizeOrganizationScopeValue(organization);
|
return (0, assistantOrganizationScopeRuntimeAdapter_1.mergeFollowupContextWithOrganizationScopeRuntime)({
|
||||||
const base = followupContext && typeof followupContext === "object" ? { ...followupContext } : {};
|
followupContext,
|
||||||
if (!normalizedOrganization) {
|
organization,
|
||||||
return followupContext && typeof followupContext === "object" ? base : null;
|
normalizeOrganizationScopeValue,
|
||||||
}
|
toNonEmptyString
|
||||||
const previousFilters = base.previous_filters && typeof base.previous_filters === "object"
|
});
|
||||||
? { ...base.previous_filters }
|
|
||||||
: {};
|
|
||||||
if (!toNonEmptyString(previousFilters.organization)) {
|
|
||||||
previousFilters.organization = normalizedOrganization;
|
|
||||||
}
|
|
||||||
base.previous_filters = previousFilters;
|
|
||||||
return base;
|
|
||||||
}
|
}
|
||||||
export function resolveSessionOrganizationScopeContextForTests(userMessage, items) {
|
export function resolveSessionOrganizationScopeContextForTests(userMessage, items) {
|
||||||
return resolveSessionOrganizationScopeContext(userMessage, items);
|
return resolveSessionOrganizationScopeContext(userMessage, items);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import {
|
||||||
|
mergeFollowupContextWithOrganizationScopeRuntime,
|
||||||
|
resolveSessionOrganizationScopeContextRuntime
|
||||||
|
} from "../src/services/assistantOrganizationScopeRuntimeAdapter";
|
||||||
|
|
||||||
|
describe("assistant organization scope runtime adapter", () => {
|
||||||
|
it("resolves selected organization from user message and promotes it to active scope", () => {
|
||||||
|
const extractKnownOrganizationsFromHistory = vi.fn(() => ["Org A", "Org B"]);
|
||||||
|
const resolveOrganizationSelectionFromMessage = vi.fn(() => "Org B");
|
||||||
|
const findLastAssistantActiveOrganization = vi.fn(() => "Org A");
|
||||||
|
const normalizeOrganizationScopeValue = vi.fn((value: unknown) =>
|
||||||
|
typeof value === "string" && value.trim() ? value.trim() : null
|
||||||
|
);
|
||||||
|
|
||||||
|
const context = resolveSessionOrganizationScopeContextRuntime({
|
||||||
|
userMessage: "по Org B покажи",
|
||||||
|
items: [] as any[],
|
||||||
|
extractKnownOrganizationsFromHistory,
|
||||||
|
resolveOrganizationSelectionFromMessage,
|
||||||
|
findLastAssistantActiveOrganization,
|
||||||
|
normalizeOrganizationScopeValue
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(context).toEqual({
|
||||||
|
knownOrganizations: ["Org A", "Org B"],
|
||||||
|
selectedOrganization: "Org B",
|
||||||
|
activeOrganization: "Org B"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back active organization to last assistant scope when selection is absent", () => {
|
||||||
|
const normalizeOrganizationScopeValue = vi.fn((value: unknown) =>
|
||||||
|
typeof value === "string" ? value.trim() : null
|
||||||
|
);
|
||||||
|
|
||||||
|
const context = resolveSessionOrganizationScopeContextRuntime({
|
||||||
|
userMessage: "просто обсуждаем",
|
||||||
|
items: [] as any[],
|
||||||
|
extractKnownOrganizationsFromHistory: () => ["Org A"],
|
||||||
|
resolveOrganizationSelectionFromMessage: () => null,
|
||||||
|
findLastAssistantActiveOrganization: () => "Org A",
|
||||||
|
normalizeOrganizationScopeValue
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(context).toEqual({
|
||||||
|
knownOrganizations: ["Org A"],
|
||||||
|
selectedOrganization: null,
|
||||||
|
activeOrganization: "Org A"
|
||||||
|
});
|
||||||
|
expect(normalizeOrganizationScopeValue).toHaveBeenCalledWith("Org A");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("merges organization into followup previous filters when organization is missing", () => {
|
||||||
|
const merged = mergeFollowupContextWithOrganizationScopeRuntime({
|
||||||
|
followupContext: {
|
||||||
|
previous_filters: {
|
||||||
|
period: "2020-07"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
organization: " Org A ",
|
||||||
|
normalizeOrganizationScopeValue: (value: unknown) =>
|
||||||
|
typeof value === "string" && value.trim() ? value.trim() : null,
|
||||||
|
toNonEmptyString: (value: unknown) =>
|
||||||
|
typeof value === "string" && value.trim().length > 0 ? value.trim() : null
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(merged).toEqual({
|
||||||
|
previous_filters: {
|
||||||
|
period: "2020-07",
|
||||||
|
organization: "Org A"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps existing organization in followup filters and returns null for empty context without org", () => {
|
||||||
|
const preserved = mergeFollowupContextWithOrganizationScopeRuntime({
|
||||||
|
followupContext: {
|
||||||
|
previous_filters: {
|
||||||
|
organization: "Org Existing"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
organization: "Org A",
|
||||||
|
normalizeOrganizationScopeValue: (value: unknown) =>
|
||||||
|
typeof value === "string" && value.trim() ? value.trim() : null,
|
||||||
|
toNonEmptyString: (value: unknown) =>
|
||||||
|
typeof value === "string" && value.trim().length > 0 ? value.trim() : null
|
||||||
|
});
|
||||||
|
const empty = mergeFollowupContextWithOrganizationScopeRuntime({
|
||||||
|
followupContext: null,
|
||||||
|
organization: "",
|
||||||
|
normalizeOrganizationScopeValue: () => null,
|
||||||
|
toNonEmptyString: () => null
|
||||||
|
});
|
||||||
|
|
||||||
|
expect((preserved as any).previous_filters.organization).toBe("Org Existing");
|
||||||
|
expect(empty).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue