mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-03 20: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
|
/// Fails the reading of stored data which holds a null where the result expects a value, so that
|
||||||
/// the result view never meets one.
|
/// the result view never meets one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
internal static readonly JsonSerializerOptions JSON_OPTIONS = new()
|
||||||
{
|
{
|
||||||
RespectNullableAnnotations = true,
|
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
|
/// 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.
|
/// character outside ASCII into an escape, cf. ToolExecutionResult.MODEL_CONTENT_OPTIONS.
|
||||||
/// </summary>
|
/// </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 databasePath = databasePath;
|
||||||
private readonly string lastCleanupPath = Path.Join(basePath, LAST_CLEANUP_FILENAME);
|
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
|
public sealed class TestHost
|
||||||
{
|
{
|
||||||
[OneTimeSetUp]
|
[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
|
// A number of types in the app hold a static logger field that is initialized from
|
||||||
|
|||||||
Reference in new issue
Block a user