mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-02 23:27:21 +00:00
Added tests which guard the stored JSON before source generation
This commit is contained in:
23 files changed
+4807
-3
No files matched your search
@@ -10,7 +10,7 @@ internal static class SwotAnalysisContentPresentation
|
||||
/// Fails the reading of stored data which holds a null where the result expects a value, so that
|
||||
/// the result view never meets one.
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||
{
|
||||
RespectNullableAnnotations = true,
|
||||
};
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ public sealed class SqliteProvenanceStoreClientImplementation(string name, strin
|
||||
/// Writes the content around a passage the way it reads: the default encoder would turn every
|
||||
/// character outside ASCII into an escape, cf. ToolExecutionResult.MODEL_CONTENT_OPTIONS.
|
||||
/// </summary>
|
||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
||||
internal static readonly JsonSerializerOptions JSON_OPTIONS = new() { Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
||||
|
||||
private readonly string databasePath = databasePath;
|
||||
private readonly string lastCleanupPath = Path.Join(basePath, LAST_CLEANUP_FILENAME);
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
{
|
||||
"chat_id": "00000000-0000-4000-8000-000000000001",
|
||||
"workspace_id": "00000000-0000-4000-8000-000000000002",
|
||||
"last_media_transcript_number": 9000000000,
|
||||
"draft": {
|
||||
"text": "text sample 3",
|
||||
"file_attachments": [
|
||||
{
|
||||
"$type": "file",
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 4",
|
||||
"file_path": "/samples/FilePath-5.txt",
|
||||
"file_size_bytes": 4000000006,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": true,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-5.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000007",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T11:08:00Z",
|
||||
"length": 4000000009
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "image",
|
||||
"source_type": "LOCAL_PATH",
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 10",
|
||||
"file_path": "/samples/FilePath-11.txt",
|
||||
"file_size_bytes": 4000000012,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": true,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-11.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000013",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T11:14:00Z",
|
||||
"length": 4000000015
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "managed_transcript",
|
||||
"original_file_name": "OriginalFileName sample 19",
|
||||
"is_staged": true,
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 16",
|
||||
"file_path": "/samples/FilePath-17.txt",
|
||||
"file_size_bytes": 4000000018,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": false,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-17.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000020",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T11:21:00Z",
|
||||
"length": 4000000022
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "managed_transcript",
|
||||
"original_file_name": "OriginalFileName sample 34",
|
||||
"is_staged": true,
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 31",
|
||||
"file_path": "/samples/FilePath-32.txt",
|
||||
"file_size_bytes": 4000000033,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": false,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-32.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000035",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T11:36:00Z",
|
||||
"length": 4000000037
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "managed_transcript",
|
||||
"original_file_name": "OriginalFileName sample 41",
|
||||
"is_staged": true,
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 38",
|
||||
"file_path": "/samples/FilePath-39.txt",
|
||||
"file_size_bytes": 4000000040,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": false,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-39.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000042",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T11:43:00Z",
|
||||
"length": 4000000044
|
||||
}
|
||||
}
|
||||
],
|
||||
"previewed_texts": [
|
||||
{
|
||||
"file_path": "/samples/FilePath-23.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000024",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T11:25:00Z",
|
||||
"length": 4000000026
|
||||
}
|
||||
},
|
||||
{
|
||||
"file_path": "/samples/FilePath-27.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000028",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T11:29:00Z",
|
||||
"length": 4000000030
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"selected_provider": "selected_provider sample 45",
|
||||
"selected_profile": "selected_profile sample 46",
|
||||
"selected_chat_template": "selected_chat_template sample 47",
|
||||
"selected_tool_ids": [
|
||||
"selected_tool_ids sample 48",
|
||||
"selected_tool_ids sample 49"
|
||||
],
|
||||
"include_date_time": true,
|
||||
"data_source_options": {
|
||||
"disable_data_sources": true,
|
||||
"automatic_data_source_selection": true,
|
||||
"automatic_validation": true,
|
||||
"retrieval_mode": "EVERY_MESSAGE",
|
||||
"preselected_data_source_ids": [
|
||||
"preselected_data_source_ids sample 50",
|
||||
"preselected_data_source_ids sample 51"
|
||||
]
|
||||
},
|
||||
"ai_selected_data_sources": [
|
||||
{
|
||||
"data_source": {
|
||||
"$type_discriminator": "LOCAL_DIRECTORY",
|
||||
"num": 7,
|
||||
"id": "id sample 52",
|
||||
"name": "name sample 53",
|
||||
"description": "description sample 54",
|
||||
"type": "LOCAL_FILE",
|
||||
"embedding_id": "embedding_id sample 55",
|
||||
"max_chunk_token_length": 46,
|
||||
"chunk_overlap_token_length": 47,
|
||||
"confidence_level": "UNTRUSTED",
|
||||
"is_enterprise_configuration": true,
|
||||
"enterprise_configuration_plugin_id": "00000000-0000-4000-8000-000000000058",
|
||||
"max_matches": 8,
|
||||
"outbound_data_restriction": "ONLY_LINKS_FROM_CHAT",
|
||||
"path": "/samples/path-59.txt",
|
||||
"file_patterns": "file_patterns sample 60",
|
||||
"exclude_patterns": "exclude_patterns sample 61",
|
||||
"max_file_size_megabytes": 52,
|
||||
"include_subdirectories": true,
|
||||
"include_hidden_files_and_folders": true
|
||||
},
|
||||
"ai_decision": {
|
||||
"id": "id sample 63",
|
||||
"reason": "reason sample 64",
|
||||
"confidence": 2.25
|
||||
},
|
||||
"selected": true
|
||||
},
|
||||
{
|
||||
"data_source": {
|
||||
"$type_discriminator": "LOCAL_FILE",
|
||||
"num": 7,
|
||||
"id": "id sample 65",
|
||||
"name": "name sample 66",
|
||||
"description": "description sample 67",
|
||||
"type": "LOCAL_FILE",
|
||||
"embedding_id": "embedding_id sample 68",
|
||||
"max_chunk_token_length": 59,
|
||||
"chunk_overlap_token_length": 60,
|
||||
"confidence_level": "UNTRUSTED",
|
||||
"is_enterprise_configuration": true,
|
||||
"enterprise_configuration_plugin_id": "00000000-0000-4000-8000-000000000071",
|
||||
"max_matches": 8,
|
||||
"outbound_data_restriction": "ONLY_LINKS_FROM_CHAT",
|
||||
"file_path": "/samples/file_path-72.txt"
|
||||
},
|
||||
"ai_decision": {
|
||||
"id": "id sample 73",
|
||||
"reason": "reason sample 74",
|
||||
"confidence": 2.25
|
||||
},
|
||||
"selected": true
|
||||
}
|
||||
],
|
||||
"data_security": "ALLOW_ANY",
|
||||
"required_provider_confidence": "UNTRUSTED",
|
||||
"required_outbound_data_restriction": {
|
||||
"restriction": "ONLY_LINKS_FROM_CHAT",
|
||||
"origin": "MAILBOX",
|
||||
"reference": "Reference sample 75"
|
||||
},
|
||||
"web_addresses_written_by_models": [
|
||||
"web_addresses_written_by_models sample 77",
|
||||
"web_addresses_written_by_models sample 76"
|
||||
],
|
||||
"name": "name sample 78",
|
||||
"system_prompt": "system_prompt sample 79",
|
||||
"blocks": [
|
||||
{
|
||||
"time": "2026-10-29T13:50:15+01:00",
|
||||
"content_type": "UNKNOWN",
|
||||
"content": {
|
||||
"$type": "text",
|
||||
"sources": [
|
||||
{
|
||||
"title": "Title sample 81",
|
||||
"url": "URL sample 82",
|
||||
"origin": "RAG"
|
||||
},
|
||||
{
|
||||
"title": "Title sample 83",
|
||||
"url": "URL sample 84",
|
||||
"origin": "RAG"
|
||||
}
|
||||
],
|
||||
"file_attachments": [
|
||||
{
|
||||
"$type": "file",
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 85",
|
||||
"file_path": "/samples/FilePath-86.txt",
|
||||
"file_size_bytes": 4000000087,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": true,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-86.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000088",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T12:29:00Z",
|
||||
"length": 4000000090
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "image",
|
||||
"source_type": "LOCAL_PATH",
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 91",
|
||||
"file_path": "/samples/FilePath-92.txt",
|
||||
"file_size_bytes": 4000000093,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": true,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-92.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000094",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T12:35:00Z",
|
||||
"length": 4000000096
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "managed_transcript",
|
||||
"original_file_name": "OriginalFileName sample 100",
|
||||
"is_staged": true,
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 97",
|
||||
"file_path": "/samples/FilePath-98.txt",
|
||||
"file_size_bytes": 4000000099,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": false,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-98.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000101",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T12:42:00Z",
|
||||
"length": 4000000103
|
||||
}
|
||||
}
|
||||
],
|
||||
"tool_invocations": [
|
||||
{
|
||||
"order": 44,
|
||||
"tool_id": "tool_id sample 105",
|
||||
"tool_name": "tool_name sample 106",
|
||||
"tool_icon": "tool_icon sample 107",
|
||||
"tool_call_id": "tool_call_id sample 108",
|
||||
"status": "SUCCESS",
|
||||
"was_executed": true,
|
||||
"status_message": "status_message sample 109",
|
||||
"arguments": {
|
||||
"argumentsKey sample 110": "arguments sample 111",
|
||||
"argumentsKey sample 112": "arguments sample 113"
|
||||
}
|
||||
},
|
||||
{
|
||||
"order": 54,
|
||||
"tool_id": "tool_id sample 115",
|
||||
"tool_name": "tool_name sample 116",
|
||||
"tool_icon": "tool_icon sample 117",
|
||||
"tool_call_id": "tool_call_id sample 118",
|
||||
"status": "SUCCESS",
|
||||
"was_executed": true,
|
||||
"status_message": "status_message sample 119",
|
||||
"arguments": {
|
||||
"argumentsKey sample 120": "arguments sample 121",
|
||||
"argumentsKey sample 122": "arguments sample 123"
|
||||
}
|
||||
}
|
||||
],
|
||||
"presentation": {
|
||||
"kind": "SWOT_ANALYSIS",
|
||||
"data": {
|
||||
"sample": 124,
|
||||
"text": "Gr\u00F6\u00DFe"
|
||||
}
|
||||
},
|
||||
"source_passages": [
|
||||
{
|
||||
"source_url": "source_url sample 125",
|
||||
"data_source_name": "data_source_name sample 126",
|
||||
"path": "/samples/path-127.txt",
|
||||
"category": "UNKNOWN",
|
||||
"type": "UNKNOWN",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000128",
|
||||
"text": "text sample 129",
|
||||
"surrounding_content": [
|
||||
"surrounding_content sample 130",
|
||||
"surrounding_content sample 131"
|
||||
],
|
||||
"page_number": 72,
|
||||
"end_page_number": 73,
|
||||
"sheet_name": "sheet_name sample 134",
|
||||
"start_row": 75,
|
||||
"end_row": 76,
|
||||
"start_line": 77,
|
||||
"end_line": 78,
|
||||
"chunk_number": 79
|
||||
},
|
||||
{
|
||||
"source_url": "source_url sample 140",
|
||||
"data_source_name": "data_source_name sample 141",
|
||||
"path": "/samples/path-142.txt",
|
||||
"category": "UNKNOWN",
|
||||
"type": "UNKNOWN",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000143",
|
||||
"text": "text sample 144",
|
||||
"surrounding_content": [
|
||||
"surrounding_content sample 145",
|
||||
"surrounding_content sample 146"
|
||||
],
|
||||
"page_number": 87,
|
||||
"end_page_number": 88,
|
||||
"sheet_name": "sheet_name sample 149",
|
||||
"start_row": 40,
|
||||
"end_row": 41,
|
||||
"start_line": 42,
|
||||
"end_line": 43,
|
||||
"chunk_number": 44
|
||||
}
|
||||
],
|
||||
"reported_usage": {
|
||||
"prompt_tokens": 45,
|
||||
"model_id": "model_id sample 156",
|
||||
"block_count": 47
|
||||
},
|
||||
"text": "text sample 158"
|
||||
},
|
||||
"role": "UNKNOWN",
|
||||
"hide_from_user": true
|
||||
},
|
||||
{
|
||||
"time": "2026-10-29T15:09:15+01:00",
|
||||
"content_type": "UNKNOWN",
|
||||
"content": {
|
||||
"$type": "image",
|
||||
"sources": [
|
||||
{
|
||||
"title": "Title sample 160",
|
||||
"url": "URL sample 161",
|
||||
"origin": "RAG"
|
||||
},
|
||||
{
|
||||
"title": "Title sample 162",
|
||||
"url": "URL sample 163",
|
||||
"origin": "RAG"
|
||||
}
|
||||
],
|
||||
"file_attachments": [
|
||||
{
|
||||
"$type": "file",
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 164",
|
||||
"file_path": "/samples/FilePath-165.txt",
|
||||
"file_size_bytes": 4000000166,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": true,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-165.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000167",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T13:48:00Z",
|
||||
"length": 4000000169
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "image",
|
||||
"source_type": "LOCAL_PATH",
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 170",
|
||||
"file_path": "/samples/FilePath-171.txt",
|
||||
"file_size_bytes": 4000000172,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": true,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-171.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000173",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T13:54:00Z",
|
||||
"length": 4000000175
|
||||
}
|
||||
},
|
||||
{
|
||||
"$type": "managed_transcript",
|
||||
"original_file_name": "OriginalFileName sample 179",
|
||||
"is_staged": true,
|
||||
"type": "IMAGE",
|
||||
"file_name": "FileName sample 176",
|
||||
"file_path": "/samples/FilePath-177.txt",
|
||||
"file_size_bytes": 4000000178,
|
||||
"is_forbidden": false,
|
||||
"is_valid": true,
|
||||
"is_image": false,
|
||||
"file_path_as_url": "/local/file?path=%2Fsamples%2FFilePath-177.txt",
|
||||
"provenance_id": "00000000-0000-4000-8000-000000000180",
|
||||
"version": {
|
||||
"last_write_utc": "2026-10-29T14:01:00Z",
|
||||
"length": 4000000182
|
||||
}
|
||||
}
|
||||
],
|
||||
"source_type": "LOCAL_PATH",
|
||||
"source": "source sample 183"
|
||||
},
|
||||
"role": "UNKNOWN",
|
||||
"hide_from_user": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"format_version": 41,
|
||||
"app_version": "AppVersion sample 2",
|
||||
"settings_version": "SettingsVersion sample 3",
|
||||
"created_at_utc": "2026-10-29T12:34:15+01:00",
|
||||
"source_data_directory": "SourceDataDirectory sample 5",
|
||||
"source_config_directory": "SourceConfigDirectory sample 6",
|
||||
"includes_data_source_indexes": true,
|
||||
"entries": [
|
||||
{
|
||||
"name": "Name sample 7",
|
||||
"size": 4000000008,
|
||||
"sha256": "Sha256 sample 9"
|
||||
},
|
||||
{
|
||||
"name": "Name sample 10",
|
||||
"size": 4000000011,
|
||||
"sha256": "Sha256 sample 12"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"format_version": 41,
|
||||
"prepared_at_utc": "2026-10-29T12:32:15+01:00",
|
||||
"backup_created_at_utc": "2026-10-29T12:33:15+01:00",
|
||||
"backup_app_version": "BackupAppVersion sample 4",
|
||||
"elements": [
|
||||
{
|
||||
"location": "CONFIG_DIRECTORY",
|
||||
"relative_path": "/samples/RelativePath-5.txt",
|
||||
"keep": [
|
||||
"Keep sample 6",
|
||||
"Keep sample 7"
|
||||
]
|
||||
},
|
||||
{
|
||||
"location": "CONFIG_DIRECTORY",
|
||||
"relative_path": "/samples/RelativePath-8.txt",
|
||||
"keep": [
|
||||
"Keep sample 9",
|
||||
"Keep sample 10"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"outcome": "REFUSED",
|
||||
"detail": "Detail sample 1",
|
||||
"prepared_at_utc": "PreparedAtUtc sample 2",
|
||||
"backup_created_at_utc": "BackupCreatedAtUtc sample 3",
|
||||
"backup_app_version": "BackupAppVersion sample 4",
|
||||
"finished_at_utc": "FinishedAtUtc sample 5"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
["Vor dem Treffer: <b>Größe</b> & \"Zitat\" für C# 14","Nach dem Treffer: 50 % – fertig, 'oder?'"]
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"Version": "V1",
|
||||
"Providers": [
|
||||
{
|
||||
"Num": 7,
|
||||
"Id": "Id sample 1",
|
||||
"InstanceName": "InstanceName sample 2",
|
||||
"UsedProvider": "OPEN_AI",
|
||||
"Model": {
|
||||
"Id": "Id sample 3",
|
||||
"DisplayName": "DisplayName sample 4",
|
||||
"IsSystemModel": false
|
||||
},
|
||||
"IsSelfHosted": true,
|
||||
"Hostname": "Hostname sample 5",
|
||||
"Host": "LM_STUDIO"
|
||||
},
|
||||
{
|
||||
"Num": 7,
|
||||
"Id": "Id sample 6",
|
||||
"InstanceName": "InstanceName sample 7",
|
||||
"UsedProvider": "OPEN_AI",
|
||||
"Model": {
|
||||
"Id": "Id sample 8",
|
||||
"DisplayName": "DisplayName sample 9",
|
||||
"IsSystemModel": false
|
||||
},
|
||||
"IsSelfHosted": true,
|
||||
"Hostname": "Hostname sample 10",
|
||||
"Host": "LM_STUDIO"
|
||||
}
|
||||
],
|
||||
"NextProviderNum": 7,
|
||||
"IsSavingEnergy": true,
|
||||
"EnableSpellchecking": true,
|
||||
"UpdateInterval": "DISABLE_UPDATES",
|
||||
"NavigationBehavior": "NEVER_EXPAND_USE_TOOLTIPS",
|
||||
"ShortcutSendBehavior": "MODIFER_ENTER_IS_SENDING",
|
||||
"PreselectChatOptions": true,
|
||||
"PreselectedChatProvider": "PreselectedChatProvider sample 11",
|
||||
"WorkspaceStorageBehavior": "STORE_CHATS_AUTOMATICALLY",
|
||||
"WorkspaceStorageTemporaryMaintenancePolicy": "DELETE_OLDER_THAN_7_DAYS",
|
||||
"PreselectIconOptions": true,
|
||||
"PreselectedIconSource": "ICONS8",
|
||||
"PreselectedIconProvider": "PreselectedIconProvider sample 12",
|
||||
"LiveTranslationDebounceIntervalMilliseconds": 53,
|
||||
"PreselectTranslationOptions": true,
|
||||
"PreselectLiveTranslation": true,
|
||||
"HideWebContentReaderForTranslation": true,
|
||||
"PreselectWebContentReaderForTranslation": true,
|
||||
"PreselectContentCleanerAgentForTranslation": true,
|
||||
"PreselectedTranslationTargetLanguage": "EN_US",
|
||||
"PreselectTranslationOtherLanguage": "PreselectTranslationOtherLanguage sample 14",
|
||||
"PreselectedTranslationProvider": "PreselectedTranslationProvider sample 15",
|
||||
"PreselectCodingOptions": true,
|
||||
"PreselectCodingCompilerMessages": true,
|
||||
"PreselectedCodingLanguage": "BASH",
|
||||
"PreselectedCodingOtherLanguage": "PreselectedCodingOtherLanguage sample 16",
|
||||
"PreselectedCodingProvider": "PreselectedCodingProvider sample 17",
|
||||
"PreselectTextSummarizerOptions": true,
|
||||
"HideWebContentReaderForTextSummarizer": true,
|
||||
"PreselectWebContentReaderForTextSummarizer": true,
|
||||
"PreselectContentCleanerAgentForTextSummarizer": true,
|
||||
"PreselectedTextSummarizerTargetLanguage": "EN_US",
|
||||
"PreselectedTextSummarizerOtherLanguage": "PreselectedTextSummarizerOtherLanguage sample 18",
|
||||
"PreselectedTextSummarizerComplexity": "SIMPLE_LANGUAGE",
|
||||
"PreselectedTextSummarizerExpertInField": "PreselectedTextSummarizerExpertInField sample 19",
|
||||
"PreselectedTextSummarizerProvider": "PreselectedTextSummarizerProvider sample 20",
|
||||
"PreselectAgentTextContentCleanerOptions": true,
|
||||
"PreselectedAgentTextContentCleanerProvider": "PreselectedAgentTextContentCleanerProvider sample 21"
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
{
|
||||
"Version": "V4",
|
||||
"Providers": [
|
||||
{
|
||||
"Num": 7,
|
||||
"Id": "Id sample 1",
|
||||
"InstanceName": "InstanceName sample 2",
|
||||
"UsedProvider": "OPEN_AI",
|
||||
"Model": {
|
||||
"Id": "Id sample 3",
|
||||
"DisplayName": "DisplayName sample 4",
|
||||
"IsSystemModel": false
|
||||
},
|
||||
"IsSelfHosted": true,
|
||||
"Hostname": "Hostname sample 5",
|
||||
"Host": "LM_STUDIO"
|
||||
},
|
||||
{
|
||||
"Num": 7,
|
||||
"Id": "Id sample 6",
|
||||
"InstanceName": "InstanceName sample 7",
|
||||
"UsedProvider": "OPEN_AI",
|
||||
"Model": {
|
||||
"Id": "Id sample 8",
|
||||
"DisplayName": "DisplayName sample 9",
|
||||
"IsSystemModel": false
|
||||
},
|
||||
"IsSelfHosted": true,
|
||||
"Hostname": "Hostname sample 10",
|
||||
"Host": "LM_STUDIO"
|
||||
}
|
||||
],
|
||||
"LLMProviders": {
|
||||
"EnforceGlobalMinimumConfidence": true,
|
||||
"GlobalMinimumConfidence": "UNTRUSTED",
|
||||
"ShowProviderConfidence": true,
|
||||
"ConfidenceScheme": "TRUST_USA_EUROPE",
|
||||
"CustomConfidenceScheme": {
|
||||
"OPEN_AI": "UNTRUSTED",
|
||||
"ANTHROPIC": "UNKNOWN"
|
||||
}
|
||||
},
|
||||
"Profiles": [
|
||||
{
|
||||
"Num": 7,
|
||||
"Id": "Id sample 11",
|
||||
"Name": "Name sample 12",
|
||||
"NeedToKnow": "NeedToKnow sample 13",
|
||||
"Actions": "Actions sample 14",
|
||||
"IsEnterpriseConfiguration": true,
|
||||
"EnterpriseConfigurationPluginId": "00000000-0000-4000-8000-000000000015"
|
||||
},
|
||||
{
|
||||
"Num": 7,
|
||||
"Id": "Id sample 16",
|
||||
"Name": "Name sample 17",
|
||||
"NeedToKnow": "NeedToKnow sample 18",
|
||||
"Actions": "Actions sample 19",
|
||||
"IsEnterpriseConfiguration": true,
|
||||
"EnterpriseConfigurationPluginId": "00000000-0000-4000-8000-000000000020"
|
||||
}
|
||||
],
|
||||
"NextProviderNum": 7,
|
||||
"NextProfileNum": 7,
|
||||
"App": {
|
||||
"LanguageBehavior": "MANUAL",
|
||||
"LanguagePluginId": "00000000-0000-4000-8000-000000000021",
|
||||
"PreferredTheme": "LIGHT",
|
||||
"IsSavingEnergy": true,
|
||||
"EnableSpellchecking": true,
|
||||
"CloseExportMessagesAutomatically": true,
|
||||
"UpdateInterval": "DISABLE_UPDATES",
|
||||
"UpdateInstallation": "AUTOMATIC",
|
||||
"NavigationBehavior": "NEVER_EXPAND_USE_TOOLTIPS",
|
||||
"StartPage": "CHAT",
|
||||
"InterfaceScreenSizes": [
|
||||
{
|
||||
"ScreenKey": "ScreenKey sample 22",
|
||||
"Size": "SYSTEM_SCALING",
|
||||
"CustomPercent": 63
|
||||
},
|
||||
{
|
||||
"ScreenKey": "ScreenKey sample 24",
|
||||
"Size": "SYSTEM_SCALING",
|
||||
"CustomPercent": 65
|
||||
}
|
||||
],
|
||||
"ShowPromptInjectionAlert": true,
|
||||
"ShowIntroduction": true,
|
||||
"ShowQuickStartGuide": true,
|
||||
"ShowLastChangelog": true,
|
||||
"ShowVision": true,
|
||||
"PreviewVisibility": "RELEASE_CANDIDATE",
|
||||
"EnabledPreviewFeatures": [
|
||||
"PRE_WRITER_MODE_2024",
|
||||
"PRE_RAG_2024"
|
||||
],
|
||||
"PreselectedProvider": "PreselectedProvider sample 26",
|
||||
"PreselectedProfile": "PreselectedProfile sample 27",
|
||||
"PreselectedChatTemplate": "PreselectedChatTemplate sample 28",
|
||||
"UseTranscriptionProvider": "UseTranscriptionProvider sample 29",
|
||||
"OpusBitrate": "KBPS_32",
|
||||
"ShortcutVoiceRecording": "ShortcutVoiceRecording sample 30",
|
||||
"ShortcutVoiceRecordingDisplayName": "ShortcutVoiceRecordingDisplayName sample 31",
|
||||
"ShortcutVoiceRecordingDisplaySource": "ShortcutVoiceRecordingDisplaySource sample 32",
|
||||
"HttpClientTimeoutSeconds": 73,
|
||||
"ShareFeatureUsageWithSelfHostedServerOperators": true,
|
||||
"ExternalHttpCustomRootCertificatesEnabled": true,
|
||||
"ExternalHttpCustomRootCertificateBundlePath": "/samples/ExternalHttpCustomRootCertificateBundlePath-34.txt",
|
||||
"ExternalHttpCustomRootCertificateAllowedHosts": [
|
||||
"ExternalHttpCustomRootCertificateAllowedHosts sample 35",
|
||||
"ExternalHttpCustomRootCertificateAllowedHosts sample 36"
|
||||
],
|
||||
"AllowUserToAddProvider": true,
|
||||
"AllowUserToAddLLMProvider": true,
|
||||
"AllowUserToAddEmbeddingProvider": true,
|
||||
"AllowUserToAddTranscriptionProvider": true,
|
||||
"ProhibitedProviders": [
|
||||
"OPEN_AI",
|
||||
"ANTHROPIC"
|
||||
],
|
||||
"AllowUserToAddMailbox": true,
|
||||
"AllowUserToImportConfigurationSnippets": true,
|
||||
"AllowUserToImportProfile": true,
|
||||
"AllowUserToImportLLMProvider": true,
|
||||
"AllowUserToImportEmbeddingProvider": true,
|
||||
"AllowUserToImportTranscriptionProvider": true,
|
||||
"AllowUserToImportChatTemplate": true,
|
||||
"AllowUserToImportERIDataSource": true,
|
||||
"AllowUserToImportDocumentAnalysisPolicy": true,
|
||||
"AllowUserToImportPlugins": true,
|
||||
"AllowUserToImportConfigurationPlugins": true,
|
||||
"AllowUserToSharePlugins": true,
|
||||
"AllowUserToBackUpData": true,
|
||||
"AllowUserToRestoreData": true,
|
||||
"ShowAdminSettings": true,
|
||||
"EnterpriseEncryptionExportFormat": "V2",
|
||||
"AddAIGeneratedContentDisclosure": true,
|
||||
"DataSourceIndexing": {
|
||||
"AutomaticRefresh": true,
|
||||
"MinimumPauseBetweenStepsSeconds": 77,
|
||||
"MaximumPauseBetweenStepsSeconds": 78
|
||||
},
|
||||
"HiddenAssistants": [
|
||||
"UNKNOWN",
|
||||
"GRAMMAR_SPELLING_ASSISTANT"
|
||||
]
|
||||
},
|
||||
"Chat": {
|
||||
"ShortcutSendBehavior": "MODIFER_ENTER_IS_SENDING",
|
||||
"LoadingProviderBehavior": "ALWAYS_USE_DEFAULT_CHAT_PROVIDER",
|
||||
"AddChatProviderBehavior": "ADDED_CHATS_USE_LATEST_PROVIDER",
|
||||
"SendToChatDataSourceBehavior": "APPLY_STANDARD_CHAT_DATA_SOURCE_OPTIONS",
|
||||
"PreselectOptions": true,
|
||||
"PreselectedProvider": "PreselectedProvider sample 39",
|
||||
"PreselectedProfile": "PreselectedProfile sample 40",
|
||||
"PreselectedChatTemplate": "PreselectedChatTemplate sample 41",
|
||||
"PreselectedDataSourcesDisabled": true,
|
||||
"PreselectedDataSourcesAutomaticSelection": true,
|
||||
"PreselectedDataSourcesAutomaticValidation": true,
|
||||
"PreselectedDataSourceIds": [
|
||||
"PreselectedDataSourceIds sample 44",
|
||||
"PreselectedDataSourceIds sample 45"
|
||||
],
|
||||
"PreselectedDataSourcesRetrievalMode": "EVERY_MESSAGE",
|
||||
"PreselectedDataSourceOptions": {
|
||||
"DisableDataSources": true,
|
||||
"AutomaticDataSourceSelection": true,
|
||||
"AutomaticValidation": true,
|
||||
"RetrievalMode": "EVERY_MESSAGE",
|
||||
"PreselectedDataSourceIds": [
|
||||
"PreselectedDataSourceIds sample 44",
|
||||
"PreselectedDataSourceIds sample 45"
|
||||
]
|
||||
},
|
||||
"ShowLatestMessageAfterLoading": true,
|
||||
"ReadChangedAttachmentsAgain": true
|
||||
},
|
||||
"Workspace": {
|
||||
"StorageBehavior": "STORE_CHATS_AUTOMATICALLY",
|
||||
"StorageTemporaryMaintenancePolicy": "DELETE_OLDER_THAN_7_DAYS",
|
||||
"DisplayBehavior": "TOGGLE_SIDEBAR",
|
||||
"IsSidebarVisible": true,
|
||||
"SplitterPosition": 1.5
|
||||
},
|
||||
"IconFinder": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectedSource": "ICONS8",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 46"
|
||||
},
|
||||
"Translation": {
|
||||
"DebounceIntervalMilliseconds": 87,
|
||||
"PreselectOptions": true,
|
||||
"PreselectLiveTranslation": true,
|
||||
"HideWebContentReader": true,
|
||||
"PreselectWebContentReader": true,
|
||||
"PreselectContentCleanerAgent": true,
|
||||
"PreselectedTargetLanguage": "EN_US",
|
||||
"PreselectOtherLanguage": "PreselectOtherLanguage sample 48",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 49"
|
||||
},
|
||||
"Coding": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectCompilerMessages": true,
|
||||
"PreselectedProgrammingLanguage": "BASH",
|
||||
"PreselectedOtherProgrammingLanguage": "PreselectedOtherProgrammingLanguage sample 50",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 51",
|
||||
"PreselectedProfile": "PreselectedProfile sample 52"
|
||||
},
|
||||
"TextSummarizer": {
|
||||
"PreselectOptions": true,
|
||||
"HideWebContentReader": true,
|
||||
"PreselectWebContentReader": true,
|
||||
"PreselectContentCleanerAgent": true,
|
||||
"PreselectedTargetLanguage": "EN_US",
|
||||
"PreselectedOtherLanguage": "PreselectedOtherLanguage sample 53",
|
||||
"PreselectedComplexity": "SIMPLE_LANGUAGE",
|
||||
"PreselectedExpertInField": "PreselectedExpertInField sample 54",
|
||||
"PreselectedImportantAspects": "PreselectedImportantAspects sample 55",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 56"
|
||||
},
|
||||
"TextContentCleaner": {
|
||||
"PreselectAgentOptions": true,
|
||||
"PreselectedAgentProvider": "PreselectedAgentProvider sample 57"
|
||||
},
|
||||
"Agenda": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectName": "PreselectName sample 58",
|
||||
"PreselectTopic": "PreselectTopic sample 59",
|
||||
"PreselectObjective": "PreselectObjective sample 60",
|
||||
"PreselectModerator": "PreselectModerator sample 61",
|
||||
"PreselectDuration": "PreselectDuration sample 62",
|
||||
"PreselectStartTime": "PreselectStartTime sample 63",
|
||||
"PreselectIntroduceParticipants": true,
|
||||
"PreselectNumberParticipants": "PEER_TO_PEER",
|
||||
"PreselectActiveParticipation": true,
|
||||
"PreselectIsMeetingVirtual": true,
|
||||
"PreselectLocation": "PreselectLocation sample 64",
|
||||
"PreselectJointDinner": true,
|
||||
"PreselectSocialActivity": true,
|
||||
"PreselectArriveAndDepart": true,
|
||||
"PreselectLunchTime": 55,
|
||||
"PreselectBreakTime": 56,
|
||||
"PreselectedTargetLanguage": "EN_US",
|
||||
"PreselectedOtherLanguage": "PreselectedOtherLanguage sample 67",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 68",
|
||||
"PreselectedProfile": "PreselectedProfile sample 69"
|
||||
},
|
||||
"GrammarSpelling": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectedTargetLanguage": "EN_US",
|
||||
"PreselectedOtherLanguage": "PreselectedOtherLanguage sample 70",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 71"
|
||||
},
|
||||
"RewriteImprove": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectedTargetLanguage": "EN_US",
|
||||
"PreselectedOtherLanguage": "PreselectedOtherLanguage sample 72",
|
||||
"PreselectedWritingStyle": "EVERYDAY",
|
||||
"PreselectedSentenceStructure": "ACTIVE",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 73"
|
||||
},
|
||||
"EMail": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectedTargetLanguage": "EN_US",
|
||||
"PreselectOtherLanguage": "PreselectOtherLanguage sample 74",
|
||||
"PreselectedWritingStyle": "BUSINESS_FORMAL",
|
||||
"Greeting": "Greeting sample 75",
|
||||
"SenderName": "SenderName sample 76",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 77",
|
||||
"PreselectedProfile": "PreselectedProfile sample 78"
|
||||
},
|
||||
"LegalCheck": {
|
||||
"PreselectOptions": true,
|
||||
"HideWebContentReader": true,
|
||||
"PreselectWebContentReader": true,
|
||||
"PreselectContentCleanerAgent": true,
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 79",
|
||||
"PreselectedProfile": "PreselectedProfile sample 80"
|
||||
},
|
||||
"Synonyms": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectedLanguage": "EN_US",
|
||||
"PreselectedOtherLanguage": "PreselectedOtherLanguage sample 81",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 82"
|
||||
},
|
||||
"MyTasks": {
|
||||
"PreselectOptions": true,
|
||||
"PreselectedTargetLanguage": "EN_US",
|
||||
"PreselectOtherLanguage": "PreselectOtherLanguage sample 83",
|
||||
"MinimumProviderConfidence": "UNTRUSTED",
|
||||
"PreselectedProvider": "PreselectedProvider sample 84",
|
||||
"PreselectedProfile": "PreselectedProfile sample 85"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"matrix_heading": "matrix_heading sample 1",
|
||||
"internal_label": "internal_label sample 2",
|
||||
"external_label": "external_label sample 3",
|
||||
"positive_label": "positive_label sample 4",
|
||||
"negative_label": "negative_label sample 5",
|
||||
"strengths": {
|
||||
"label": "label sample 6",
|
||||
"empty_message": "empty_message sample 7",
|
||||
"findings": [
|
||||
{
|
||||
"summary": "summary sample 8",
|
||||
"explanation": "explanation sample 9"
|
||||
},
|
||||
{
|
||||
"summary": "summary sample 10",
|
||||
"explanation": "explanation sample 11"
|
||||
}
|
||||
]
|
||||
},
|
||||
"weaknesses": {
|
||||
"label": "label sample 12",
|
||||
"empty_message": "empty_message sample 13",
|
||||
"findings": [
|
||||
{
|
||||
"summary": "summary sample 14",
|
||||
"explanation": "explanation sample 15"
|
||||
},
|
||||
{
|
||||
"summary": "summary sample 16",
|
||||
"explanation": "explanation sample 17"
|
||||
}
|
||||
]
|
||||
},
|
||||
"opportunities": {
|
||||
"label": "label sample 18",
|
||||
"empty_message": "empty_message sample 19",
|
||||
"findings": [
|
||||
{
|
||||
"summary": "summary sample 20",
|
||||
"explanation": "explanation sample 21"
|
||||
},
|
||||
{
|
||||
"summary": "summary sample 22",
|
||||
"explanation": "explanation sample 23"
|
||||
}
|
||||
]
|
||||
},
|
||||
"threats": {
|
||||
"label": "label sample 24",
|
||||
"empty_message": "empty_message sample 25",
|
||||
"findings": [
|
||||
{
|
||||
"summary": "summary sample 26",
|
||||
"explanation": "explanation sample 27"
|
||||
},
|
||||
{
|
||||
"summary": "summary sample 28",
|
||||
"explanation": "explanation sample 29"
|
||||
}
|
||||
]
|
||||
},
|
||||
"overall_assessment": {
|
||||
"label": "label sample 30",
|
||||
"text": "text sample 31"
|
||||
},
|
||||
"prioritized_actions": {
|
||||
"label": "label sample 32",
|
||||
"empty_message": "empty_message sample 33",
|
||||
"items": [
|
||||
{
|
||||
"action": "action sample 34",
|
||||
"rationale": "rationale sample 35",
|
||||
"addressed_factors": [
|
||||
"addressed_factors sample 36",
|
||||
"addressed_factors sample 37"
|
||||
]
|
||||
},
|
||||
{
|
||||
"action": "action sample 38",
|
||||
"rationale": "rationale sample 39",
|
||||
"addressed_factors": [
|
||||
"addressed_factors sample 40",
|
||||
"addressed_factors sample 41"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"moderation_notes": {
|
||||
"label": "label sample 42",
|
||||
"empty_message": "empty_message sample 43",
|
||||
"items": [
|
||||
{
|
||||
"change": "change sample 44",
|
||||
"reason": "reason sample 45"
|
||||
},
|
||||
{
|
||||
"change": "change sample 46",
|
||||
"reason": "reason sample 47"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Finds the values of one JSON document which another one lacks.
|
||||
/// </summary>
|
||||
public static class JsonContainment
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the path of every value in the expected document which the actual one lacks or holds otherwise.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Properties only the actual document has don't count, e.g., one added to a type after the expected document
|
||||
/// was written. A property which is null in the expected document may be missing in the actual one, since
|
||||
/// some properties are only written with a value. Arrays have to hold the same elements, in any order: an
|
||||
/// ImmutableHashSet of strings orders them by their hash codes, which differ from process to process.
|
||||
/// </remarks>
|
||||
/// <param name="expected">The document whose values have to be there.</param>
|
||||
/// <param name="actual">The document which has to hold them.</param>
|
||||
/// <returns>One line per value, with its path and both values.</returns>
|
||||
public static IReadOnlyList<string> FindMissing(JsonNode? expected, JsonNode? actual)
|
||||
{
|
||||
var missing = new List<string>();
|
||||
Compare(expected, actual, "$", missing);
|
||||
return missing;
|
||||
}
|
||||
|
||||
private static void Compare(JsonNode? expected, JsonNode? actual, string path, List<string> missing)
|
||||
{
|
||||
switch (expected)
|
||||
{
|
||||
case JsonObject expectedObject when actual is JsonObject actualObject:
|
||||
foreach (var (name, expectedValue) in expectedObject)
|
||||
{
|
||||
if (actualObject.TryGetPropertyValue(name, out var actualValue))
|
||||
Compare(expectedValue, actualValue, $"{path}.{name}", missing);
|
||||
else if (expectedValue is not null)
|
||||
missing.Add($"{path}.{name}: missing, expected {expectedValue.ToJsonString()}");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case JsonArray expectedArray when actual is JsonArray actualArray && expectedArray.Count == actualArray.Count:
|
||||
var unmatched = actualArray.ToList();
|
||||
for (var index = 0; index < expectedArray.Count; index++)
|
||||
{
|
||||
var match = unmatched.FindIndex(candidate => FindMissing(expectedArray[index], candidate).Count is 0);
|
||||
if (match < 0)
|
||||
{
|
||||
missing.Add($"{path}[{index}]: no element holds {expectedArray[index]?.ToJsonString() ?? "null"}");
|
||||
continue;
|
||||
}
|
||||
|
||||
unmatched.RemoveAt(match);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
if (!JsonNode.DeepEquals(expected, actual))
|
||||
missing.Add($"{path}: {actual?.ToJsonString() ?? "null"}, expected {expected?.ToJsonString() ?? "null"}");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Describes the contract System.Text.Json uses for a type as text, so that two contracts can be compared and
|
||||
/// a difference shows up readable in the message of a test.
|
||||
/// </summary>
|
||||
public static class JsonContractDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes everything in a contract which decides what gets written and what a read accepts.
|
||||
/// </summary>
|
||||
/// <param name="typeInfo">The contract.</param>
|
||||
/// <returns>One line for the type, and one line per property, polymorphism, and callback.</returns>
|
||||
public static string Describe(JsonTypeInfo typeInfo)
|
||||
{
|
||||
var text = new StringBuilder();
|
||||
text.AppendLine($"{TypeName(typeInfo.Type)}: {typeInfo.Kind}, converter {ConverterName(typeInfo.Converter)}, created {(typeInfo.CreateObject is null ? "otherwise" : "without arguments")}");
|
||||
text.AppendLine($" handling: numbers {typeInfo.NumberHandling}, unmapped members {typeInfo.UnmappedMemberHandling}, object creation {typeInfo.PreferredPropertyObjectCreationHandling}");
|
||||
text.AppendLine($" callbacks: serializing {typeInfo.OnSerializing is not null}, serialized {typeInfo.OnSerialized is not null}, deserializing {typeInfo.OnDeserializing is not null}, deserialized {typeInfo.OnDeserialized is not null}");
|
||||
if (typeInfo.KeyType is { } keyType)
|
||||
text.AppendLine($" key: {TypeName(keyType)}");
|
||||
|
||||
if (typeInfo.ElementType is { } elementType)
|
||||
text.AppendLine($" element: {TypeName(elementType)}");
|
||||
|
||||
if (typeInfo.PolymorphismOptions is { } polymorphism)
|
||||
{
|
||||
text.AppendLine($" polymorphism: discriminator {polymorphism.TypeDiscriminatorPropertyName}, unrecognized ignored {polymorphism.IgnoreUnrecognizedTypeDiscriminators}, unknown types {polymorphism.UnknownDerivedTypeHandling}");
|
||||
foreach (var derivedType in polymorphism.DerivedTypes)
|
||||
text.AppendLine($" derived: {TypeName(derivedType.DerivedType)} as {derivedType.TypeDiscriminator}");
|
||||
}
|
||||
|
||||
foreach (var property in typeInfo.Properties)
|
||||
text.AppendLine($" property {property.Name}: {TypeName(property.PropertyType)}, order {property.Order}, get {property.Get is not null}, set {property.Set is not null}, required {property.IsRequired}, extension data {property.IsExtensionData}, conditional {property.ShouldSerialize is not null}, converter {ConverterName(property.CustomConverter)}, constructor parameter {property.AssociatedParameter?.Name ?? "none"}, nullable get {property.IsGetNullable}, nullable set {property.IsSetNullable}, numbers {property.NumberHandling}, object creation {property.ObjectCreationHandling}");
|
||||
|
||||
return text.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the name of a type the way C# writes it, e.g., Dictionary<String, Int32>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type.</param>
|
||||
public static string TypeName(Type type)
|
||||
{
|
||||
var arity = type.Name.IndexOf('`');
|
||||
var name = arity < 0 ? type.Name : $"{type.Name[..arity]}<{string.Join(", ", type.GetGenericArguments().Select(TypeName))}>";
|
||||
return type is { IsNested: true, IsGenericParameter: false } ?$"{TypeName(type.DeclaringType!)}.{name}" : name;
|
||||
}
|
||||
|
||||
//
|
||||
// Reflection reads the constructor parameters of a record through converters of its own, whose names end
|
||||
// with "WithReflection". Source generation does the same with the plain ones, which read the same JSON:
|
||||
//
|
||||
private static string ConverterName(JsonConverter? converter) => converter is null ? "none" : TypeName(converter.GetType()).Replace("WithReflection", string.Empty, StringComparison.Ordinal);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the contracts AI Studio uses for its JSON match those reflection builds.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// AI Studio wrote all its files with contracts which reflection built. Any other contract, e.g., one which
|
||||
/// source generation builds, has to match them in every detail, or AI Studio no longer reads what it wrote
|
||||
/// before: a property with another name, without a setter, or without its converter silently loses its value.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class JsonContractTests
|
||||
{
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.Stored))]
|
||||
public void TheContractsMatchThoseOfReflection(JsonTestRoot root)
|
||||
{
|
||||
var baseline = JsonReflectionBaseline.Of(root.Options);
|
||||
var expected = JsonTypeGraph.Collect(root.Type, baseline).Select(type => JsonContractDescription.Describe(baseline.GetTypeInfo(type)));
|
||||
var actual = JsonTypeGraph.Collect(root.Type, root.Options).Select(type => JsonContractDescription.Describe(root.Options.GetTypeInfo(type)));
|
||||
|
||||
Assert.That(actual, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Checks that a property missing in the JSON keeps the value a new object starts with.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Files written by an older version of AI Studio lack every property added since. Reading them has to give
|
||||
/// those properties their defaults, as a new object has them, e.g., a sub-object of the settings instead of
|
||||
/// null. Source generation in .NET 10 breaks this for init-only properties: it sets them all while creating
|
||||
/// the object, and a property missing in the JSON gets the default of its type instead (dotnet/runtime#84484).
|
||||
/// Hence, the types AI Studio stores use set instead of init.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class JsonDefaultsTests
|
||||
{
|
||||
[TestCaseSource(typeof(JsonTestRoots), nameof(JsonTestRoots.Stored))]
|
||||
public void AnEmptyObjectGetsTheDefaultsOfANewOne(JsonTestRoot root)
|
||||
{
|
||||
var failures = new List<string>();
|
||||
foreach (var type in JsonTypeGraph.Collect(root.Type, root.Options))
|
||||
{
|
||||
//
|
||||
// A type which gets created through its constructor receives every value as an argument, so nothing
|
||||
// gets lost there. A required property refuses an empty object anyway:
|
||||
//
|
||||
var typeInfo = root.Options.GetTypeInfo(type);
|
||||
if (typeInfo is not { Kind: JsonTypeInfoKind.Object, CreateObject: { } createObject } || typeInfo.Properties.Any(property => property.IsRequired))
|
||||
continue;
|
||||
|
||||
var newObject = createObject();
|
||||
var otherNewObject = createObject();
|
||||
var readObject = JsonSerializer.Deserialize("{}", typeInfo)!;
|
||||
foreach (var property in typeInfo.Properties.Where(property => property.Get is not null))
|
||||
{
|
||||
var expected = ValueAsJson(property, newObject, root.Options);
|
||||
var actual = ValueAsJson(property, readObject, root.Options);
|
||||
|
||||
//
|
||||
// Some properties start with a value of their own each time, e.g., a new ID. There, the read object
|
||||
// just needs any value but the default of the type:
|
||||
//
|
||||
if (expected == ValueAsJson(property, otherNewObject, root.Options))
|
||||
{
|
||||
if (actual != expected)
|
||||
failures.Add($"{JsonContractDescription.TypeName(type)}.{property.Name}: read as {actual}, while a new object has {expected}.");
|
||||
}
|
||||
else if (actual == DefaultAsJson(property.PropertyType, root.Options))
|
||||
failures.Add($"{JsonContractDescription.TypeName(type)}.{property.Name}: read as {actual}, while a new object gets a value of its own.");
|
||||
}
|
||||
}
|
||||
|
||||
Assert.That(failures, Is.Empty);
|
||||
}
|
||||
|
||||
private static string ValueAsJson(JsonPropertyInfo property, object instance, JsonSerializerOptions options) => JsonSerializer.Serialize(property.Get!(instance), property.PropertyType, options);
|
||||
|
||||
private static string DefaultAsJson(Type type, JsonSerializerOptions options) => type.IsValueType ? JsonSerializer.Serialize(Activator.CreateInstance(type), type, options) : "null";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// A stored file in the fixtures, together with the type and the options AI Studio reads it with.
|
||||
/// </summary>
|
||||
/// <param name="FileName">The name of the file in the folder Fixtures next to the tests.</param>
|
||||
/// <param name="Type">The type AI Studio reads the file as.</param>
|
||||
/// <param name="Options">The options AI Studio reads and writes the file with.</param>
|
||||
public sealed record JsonGoldenFile(string FileName, Type Type, JsonSerializerOptions Options)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => this.FileName;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using AIStudio.Assistants.SWOTAnalysis;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Settings.DataModel.PreviousModels;
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Tools.Databases.ProvenanceStore;
|
||||
using AIStudio.Tools.DataBackup;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
using Version = AIStudio.Settings.Version;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Reads and writes stored files of AI Studio, and checks that nothing gets lost and nothing gets written
|
||||
/// otherwise than with the contracts of reflection.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The files in the folder Fixtures were written with the contracts of reflection, before AI Studio got
|
||||
/// contracts of its own, and every property holds a value. They are made up: no real chat or setting of
|
||||
/// anybody is among them. A property added later is missing in them, which the tests allow.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class JsonGoldenFileTests
|
||||
{
|
||||
private static IEnumerable<JsonGoldenFile> GoldenFiles()
|
||||
{
|
||||
TestHost.PrepareStaticState();
|
||||
yield return new("settings.json", typeof(Data), SettingsManager.JSON_OPTIONS);
|
||||
yield return new("settings-v1.json", typeof(DataV1V3), SettingsManager.JSON_OPTIONS);
|
||||
yield return new("settings-v4.json", typeof(DataV4), SettingsManager.JSON_OPTIONS);
|
||||
yield return new("settings-v5.json", typeof(DataV5), SettingsManager.JSON_OPTIONS);
|
||||
yield return new("chat.json", typeof(ChatThread), WorkspaceBehaviour.JSON_OPTIONS);
|
||||
yield return new("swot-analysis.json", typeof(SwotAnalysisResult), SwotAnalysisContentPresentation.JSON_OPTIONS);
|
||||
yield return new("data-backup-manifest.json", typeof(DataBackupManifest), DataBackupFormat.JSON_OPTIONS);
|
||||
yield return new("data-restore-plan.json", typeof(DataRestorePlan), DataBackupFormat.JSON_OPTIONS);
|
||||
yield return new("data-restore-report.json", typeof(DataRestoreReport), DataBackupFormat.JSON_OPTIONS);
|
||||
yield return new("provenance-surrounding-content.json", typeof(List<string>), SqliteProvenanceStoreClientImplementation.JSON_OPTIONS);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GoldenFiles))]
|
||||
public void AStoredFileIsWrittenAgainAsWithReflection(JsonGoldenFile goldenFile)
|
||||
{
|
||||
var json = ReadFixture(goldenFile.FileName);
|
||||
var baseline = JsonReflectionBaseline.Of(goldenFile.Options);
|
||||
|
||||
var writtenAgain = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, goldenFile.Type, goldenFile.Options), goldenFile.Type, goldenFile.Options);
|
||||
var writtenAgainByReflection = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, goldenFile.Type, baseline), goldenFile.Type, baseline);
|
||||
|
||||
Assert.That(writtenAgain, Is.EqualTo(writtenAgainByReflection));
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(GoldenFiles))]
|
||||
public void AStoredFileKeepsAllItsValues(JsonGoldenFile goldenFile)
|
||||
{
|
||||
var json = ReadFixture(goldenFile.FileName);
|
||||
var writtenAgain = JsonSerializer.Serialize(JsonSerializer.Deserialize(json, goldenFile.Type, goldenFile.Options), goldenFile.Type, goldenFile.Options);
|
||||
|
||||
Assert.That(JsonContainment.FindMissing(JsonNode.Parse(json), JsonNode.Parse(writtenAgain)), Is.Empty);
|
||||
}
|
||||
|
||||
[TestCase("settings-v1.json", Version.V1)]
|
||||
[TestCase("settings-v4.json", Version.V4)]
|
||||
[TestCase("settings-v5.json", Version.V5)]
|
||||
public void OldSettingsMigrateAsWithReflection(string fileName, Version version)
|
||||
{
|
||||
var json = ReadFixture(fileName);
|
||||
var baseline = JsonReflectionBaseline.Of(SettingsManager.JSON_OPTIONS);
|
||||
|
||||
var migrated = SettingsMigrations.Migrate(NullLogger<SettingsManager>.Instance, version, json, SettingsManager.JSON_OPTIONS);
|
||||
var migratedByReflection = SettingsMigrations.Migrate(NullLogger<SettingsManager>.Instance, version, json, baseline);
|
||||
|
||||
Assert.That(JsonSerializer.Serialize(migrated, SettingsManager.JSON_OPTIONS), Is.EqualTo(JsonSerializer.Serialize(migratedByReflection, baseline)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a file from the fixtures next to this test.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Read from the source tree, the way the other fixtures are, so the file needs no entry in the project file.
|
||||
/// </remarks>
|
||||
private static string ReadFixture(string fileName, [CallerFilePath] string sourceFilePath = "") => File.ReadAllText(Path.Combine(Path.GetDirectoryName(sourceFilePath)!, "Fixtures", fileName));
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// The options of AI Studio as System.Text.Json uses them without source generation: the same settings, but
|
||||
/// with contracts which reflection builds.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// AI Studio stored all its files with such contracts, so they are the baseline every other contract has to
|
||||
/// match. Each baseline is built once per options, since reflection builds its contracts once per options, too.
|
||||
/// </remarks>
|
||||
public static class JsonReflectionBaseline
|
||||
{
|
||||
private static readonly ConditionalWeakTable<JsonSerializerOptions, JsonSerializerOptions> BASELINES = new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a copy of the given options, whose contracts reflection builds.
|
||||
/// </summary>
|
||||
/// <param name="options">The options AI Studio uses.</param>
|
||||
public static JsonSerializerOptions Of(JsonSerializerOptions options) => BASELINES.GetValue(options, static original => new JsonSerializerOptions(original)
|
||||
{
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// A type which AI Studio reads or writes as JSON on its own, together with the options it uses for that.
|
||||
/// </summary>
|
||||
/// <param name="Type">The type which gets serialized. Whatever it holds gets checked along with it.</param>
|
||||
/// <param name="Options">The options AI Studio passes to System.Text.Json for this type.</param>
|
||||
public sealed record JsonTestRoot(Type Type, JsonSerializerOptions Options)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string ToString() => JsonContractDescription.TypeName(this.Type);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using AIStudio.Assistants.SWOTAnalysis;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Settings.DataModel.PreviousModels;
|
||||
using AIStudio.Tools;
|
||||
using AIStudio.Tools.Databases.ProvenanceStore;
|
||||
using AIStudio.Tools.DataBackup;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// The types which AI Studio reads or writes as JSON on their own, for the tests which compare the contracts of
|
||||
/// System.Text.Json.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only the outermost types belong here: whatever they hold gets checked along with them. Add a type whenever
|
||||
/// AI Studio starts to store a new kind of file, with the options it stores the file with.
|
||||
/// </remarks>
|
||||
public static class JsonTestRoots
|
||||
{
|
||||
/// <summary>
|
||||
/// The data AI Studio keeps on the disk of the user.
|
||||
/// </summary>
|
||||
public static IEnumerable<TestCaseData> Stored()
|
||||
{
|
||||
TestHost.PrepareStaticState();
|
||||
|
||||
// Chats, in the workspaces, among the temporary chats, and in chat archives:
|
||||
yield return Case(new(typeof(ChatThread), WorkspaceBehaviour.JSON_OPTIONS));
|
||||
|
||||
// The settings, and their older versions, which the migrations still read:
|
||||
yield return Case(new(typeof(Data), SettingsManager.JSON_OPTIONS));
|
||||
yield return Case(new(typeof(DataV1V3), SettingsManager.JSON_OPTIONS));
|
||||
yield return Case(new(typeof(DataV4), SettingsManager.JSON_OPTIONS));
|
||||
yield return Case(new(typeof(DataV5), SettingsManager.JSON_OPTIONS));
|
||||
|
||||
//
|
||||
// The values of managed settings, which get written and read on their own, as the type of their
|
||||
// setting. That type is often an interface, such as ISet<string>, which the settings themselves
|
||||
// never use:
|
||||
//
|
||||
foreach (var valueType in ManagedSettingValueTypes())
|
||||
yield return Case(new(valueType, SettingsManager.JSON_OPTIONS));
|
||||
|
||||
// The SWOT analysis, which a chat stores as the presentation of a message:
|
||||
yield return Case(new(typeof(SwotAnalysisResult), SwotAnalysisContentPresentation.JSON_OPTIONS));
|
||||
|
||||
// Backups and their restore:
|
||||
yield return Case(new(typeof(DataBackupManifest), DataBackupFormat.JSON_OPTIONS));
|
||||
yield return Case(new(typeof(DataRestorePlan), DataBackupFormat.JSON_OPTIONS));
|
||||
yield return Case(new(typeof(DataRestoreReport), DataBackupFormat.JSON_OPTIONS));
|
||||
|
||||
// The text around a passage which the provenance store keeps:
|
||||
yield return Case(new(typeof(IReadOnlyList<string>), SqliteProvenanceStoreClientImplementation.JSON_OPTIONS));
|
||||
yield return Case(new(typeof(List<string>), SqliteProvenanceStoreClientImplementation.JSON_OPTIONS));
|
||||
}
|
||||
|
||||
private static TestCaseData Case(JsonTestRoot root)
|
||||
{
|
||||
//
|
||||
// The same System.Text.Json does with the first call of Serialize or Deserialize. Only then, the
|
||||
// options hand out contracts:
|
||||
//
|
||||
root.Options.MakeReadOnly(populateMissingResolver: true);
|
||||
return new TestCaseData(root).SetArgDisplayNames(root.ToString());
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> ManagedSettingValueTypes()
|
||||
{
|
||||
//
|
||||
// Creating the settings registers every managed setting, just like the start of the app does:
|
||||
//
|
||||
_ = new Data();
|
||||
return ManagedConfiguration.RegisteredSettings
|
||||
.Select(setting => setting.GetType().GetGenericArguments()[1])
|
||||
.Distinct()
|
||||
.OrderBy(JsonContractDescription.TypeName, StringComparer.Ordinal);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace AIStudio.Tests.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Collects every type System.Text.Json meets while it reads or writes a root.
|
||||
/// </summary>
|
||||
public static class JsonTypeGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the root and every type it holds: the types of its properties, the keys and elements of its
|
||||
/// collections, and its derived types, each of them once and in the order they were met.
|
||||
/// </summary>
|
||||
/// <param name="root">The type to start with.</param>
|
||||
/// <param name="options">The options whose contracts tell what a type holds.</param>
|
||||
public static IReadOnlyList<Type> Collect(Type root, JsonSerializerOptions options)
|
||||
{
|
||||
var types = new List<Type>();
|
||||
Visit(root, options, [], types);
|
||||
return types;
|
||||
}
|
||||
|
||||
private static void Visit(Type type, JsonSerializerOptions options, HashSet<Type> visited, List<Type> types)
|
||||
{
|
||||
if (!visited.Add(type))
|
||||
return;
|
||||
|
||||
types.Add(type);
|
||||
var typeInfo = options.GetTypeInfo(type);
|
||||
foreach (var property in typeInfo.Properties)
|
||||
Visit(property.PropertyType, options, visited, types);
|
||||
|
||||
if (typeInfo.KeyType is { } keyType)
|
||||
Visit(keyType, options, visited, types);
|
||||
|
||||
if (typeInfo.ElementType is { } elementType)
|
||||
Visit(elementType, options, visited, types);
|
||||
|
||||
if (typeInfo.PolymorphismOptions is { } polymorphism)
|
||||
foreach (var derivedType in polymorphism.DerivedTypes)
|
||||
Visit(derivedType.DerivedType, options, visited, types);
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -12,7 +12,16 @@ namespace AIStudio.Tests;
|
||||
public sealed class TestHost
|
||||
{
|
||||
[OneTimeSetUp]
|
||||
public void PrepareStaticApplicationState()
|
||||
public void PrepareStaticApplicationState() => PrepareStaticState();
|
||||
|
||||
/// <summary>
|
||||
/// Fills the static state of the app which the tests need.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// NUnit builds the cases of a TestCaseSource while it discovers the tests, before this fixture runs.
|
||||
/// A source which touches a type of the app with a static logger calls this first.
|
||||
/// </remarks>
|
||||
public static void PrepareStaticState()
|
||||
{
|
||||
//
|
||||
// A number of types in the app hold a static logger field that is initialized from
|
||||
|
||||
Reference in new issue
Block a user