mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-04 05:07:21 +00:00
Upgraded to .NET 10 (#1147)
This commit is contained in:
1 parent
faae2951f7
commit
fc7399e81b
327 files changed
+3059
-3049
No files matched your search
@@ -339,7 +339,7 @@ jobs:
|
|||||||
-y \
|
-y \
|
||||||
flathub \
|
flathub \
|
||||||
"org.freedesktop.Sdk//${FLATPAK_FREEDESKTOP_VERSION}" \
|
"org.freedesktop.Sdk//${FLATPAK_FREEDESKTOP_VERSION}" \
|
||||||
"org.freedesktop.Sdk.Extension.dotnet9//${FLATPAK_FREEDESKTOP_VERSION}"
|
"org.freedesktop.Sdk.Extension.dotnet10//${FLATPAK_FREEDESKTOP_VERSION}"
|
||||||
|
|
||||||
tools_dir="$RUNNER_TEMP/flatpak-sync-tools"
|
tools_dir="$RUNNER_TEMP/flatpak-sync-tools"
|
||||||
mkdir -p "$tools_dir"
|
mkdir -p "$tools_dir"
|
||||||
@@ -793,7 +793,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Read the toolchain versions from the metadata
|
- name: Read the toolchain versions from the metadata
|
||||||
run: |
|
run: |
|
||||||
# The .NET SDK version. The format is '9.0.205 (commit 3e1383b780)',
|
# The .NET SDK version. The format is '10.0.114 (commit 0427b9abbe)',
|
||||||
# so we extract the version number alone:
|
# so we extract the version number alone:
|
||||||
dotnet_sdk_version=$(sed -n '4p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
dotnet_sdk_version=$(sed -n '4p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/')
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ MindWork AI Studio is a cross-platform desktop application for interacting with
|
|||||||
|
|
||||||
**Key Architecture Points:**
|
**Key Architecture Points:**
|
||||||
- **Runtime:** Rust-based Tauri v2 application providing the native window, system integration, and IPC layer
|
- **Runtime:** Rust-based Tauri v2 application providing the native window, system integration, and IPC layer
|
||||||
- **App:** .NET 9 Blazor Server application providing the UI and core functionality
|
- **App:** .NET 10 Blazor Server application providing the UI and core functionality
|
||||||
- **Communication:** The Rust runtime and .NET app communicate via HTTPS with TLS certificates generated at startup
|
- **Communication:** The Rust runtime and .NET app communicate via HTTPS with TLS certificates generated at startup
|
||||||
- **Providers:** Multi-provider architecture supporting OpenAI, Anthropic, Google, Mistral, Perplexity, self-hosted models, and others
|
- **Providers:** Multi-provider architecture supporting OpenAI, Anthropic, Google, Mistral, Perplexity, self-hosted models, and others
|
||||||
- **Plugin System:** Lua-based plugin system for language packs, configuration, assistants, and model knowledge
|
- **Plugin System:** Lua-based plugin system for language packs, configuration, assistants, and model knowledge
|
||||||
@@ -89,7 +89,7 @@ MindWork AI Studio is a cross-platform desktop application for interacting with
|
|||||||
## Building
|
## Building
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
- .NET 9 SDK
|
- .NET 10 SDK
|
||||||
- Rust toolchain (stable)
|
- Rust toolchain (stable)
|
||||||
- Tauri v2 CLI
|
- Tauri v2 CLI
|
||||||
- Tauri prerequisites (platform-specific dependencies)
|
- Tauri prerequisites (platform-specific dependencies)
|
||||||
@@ -114,7 +114,7 @@ failures (for reference: https://github.com/openai/codex/issues/4915). This appl
|
|||||||
Instead, use the JetBrains IDE MCP servers. They execute in the IDE process, which runs outside the
|
Instead, use the JetBrains IDE MCP servers. They execute in the IDE process, which runs outside the
|
||||||
agent sandbox:
|
agent sandbox:
|
||||||
|
|
||||||
- `rider` for the .NET solution at `app/MindWork AI Studio.sln`
|
- `rider` for the .NET solution at `app/MindWork AI Studio.slnx`
|
||||||
- `rustrover` for the Rust runtime at `runtime/`
|
- `rustrover` for the Rust runtime at `runtime/`
|
||||||
|
|
||||||
Pass the `rootFolder` parameter on every call, e.g. the absolute path of the `app` directory for Rider
|
Pass the `rootFolder` parameter on every call, e.g. the absolute path of the `app` directory for Rider
|
||||||
@@ -169,7 +169,7 @@ again yourself when `metadata.txt` changes during the session, e.g., after a mer
|
|||||||
**Building and testing** happen in the shell:
|
**Building and testing** happen in the shell:
|
||||||
|
|
||||||
- The quality gate, the same as in the pipeline: `cd app/Build && dotnet run verify`. It compiles both
|
- The quality gate, the same as in the pipeline: `cd app/Build && dotnet run verify`. It compiles both
|
||||||
the .NET app and the Rust runtime on its way. Narrower checks: `dotnet build "app/MindWork AI Studio.sln"`,
|
the .NET app and the Rust runtime on its way. Narrower checks: `dotnet build "app/MindWork AI Studio.slnx"`,
|
||||||
`dotnet test` in `app/Tests`, and `cargo test` or `cargo clippy --all-targets -- -D warnings` in `runtime/`.
|
`dotnet test` in `app/Tests`, and `cargo test` or `cargo clippy --all-targets -- -D warnings` in `runtime/`.
|
||||||
- A cold build of the Rust runtime takes several minutes: run the gate and cargo in the background, or
|
- A cold build of the Rust runtime takes several minutes: run the gate and cargo in the background, or
|
||||||
give them the longest timeout available.
|
give them the longest timeout available.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="[2] Start .NET Server" type="DotNetProject" factoryName=".NET Project">
|
<configuration default="false" name="[2] Start .NET Server" type="DotNetProject" factoryName=".NET Project">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/MindWork AI Studio/bin/Debug/net9.0/osx-arm64/mindworkAIStudio" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/MindWork AI Studio/bin/Debug/net10.0/osx-arm64/mindworkAIStudio" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/MindWork AI Studio" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/MindWork AI Studio" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net9.0" />
|
<option name="PROJECT_TFM" value="net10.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Collect I18N content" run_configuration_type="ShConfigurationType" />
|
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Collect I18N content" run_configuration_type="ShConfigurationType" />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<RootNamespace>Build</RootNamespace>
|
<RootNamespace>Build</RootNamespace>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<PackageReference Include="Cocona" Version="2.2.0" />
|
<PackageReference Include="Cocona" Version="2.2.0" />
|
||||||
|
|
||||||
<!-- Pins Cocona's transitive Microsoft.Extensions.Hosting 6.0.0, which pulled in the vulnerable System.Text.Json 6.0.0 (GHSA-8g4q-xg66-9fp4) -->
|
<!-- Pins Cocona's transitive Microsoft.Extensions.Hosting 6.0.0, which pulled in the vulnerable System.Text.Json 6.0.0 (GHSA-8g4q-xg66-9fp4) -->
|
||||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.22" />
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.14" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Build.Tools;
|
|||||||
|
|
||||||
public static class Environment
|
public static class Environment
|
||||||
{
|
{
|
||||||
public const string DOTNET_VERSION = "net9.0";
|
public const string DOTNET_VERSION = "net10.0";
|
||||||
public const string BUILD_OFFLINE_ENVIRONMENT_VARIABLE = "AI_STUDIO_BUILD_OFFLINE";
|
public const string BUILD_OFFLINE_ENVIRONMENT_VARIABLE = "AI_STUDIO_BUILD_OFFLINE";
|
||||||
public static readonly Encoding UTF8_NO_BOM = new UTF8Encoding(false);
|
public static readonly Encoding UTF8_NO_BOM = new UTF8Encoding(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MindWork AI Studio", "MindWork AI Studio\MindWork AI Studio.csproj", "{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceCodeRules", "SourceCodeRules\SourceCodeRules\SourceCodeRules.csproj", "{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build Script", "Build\Build Script.csproj", "{447A5590-68E1-4EF8-9451-A41AF5FBE571}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedTools", "SharedTools\SharedTools.csproj", "{969C74DF-7678-4CD5-B269-D03E1ECA3D2A}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceGeneratedMappings", "SourceGeneratedMappings\SourceGeneratedMappings.csproj", "{4D7141D5-9C22-4D85-B748-290D15FF484C}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{CD46329B-D135-4594-9A70-55D3480F8FEE}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrimSmokeTest", "TrimSmokeTest\TrimSmokeTest.csproj", "{0F4B3B3C-E8C9-4CDC-A247-5029D777DFC2}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{447A5590-68E1-4EF8-9451-A41AF5FBE571}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{447A5590-68E1-4EF8-9451-A41AF5FBE571}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{447A5590-68E1-4EF8-9451-A41AF5FBE571}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{447A5590-68E1-4EF8-9451-A41AF5FBE571}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{4D7141D5-9C22-4D85-B748-290D15FF484C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{4D7141D5-9C22-4D85-B748-290D15FF484C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{4D7141D5-9C22-4D85-B748-290D15FF484C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{4D7141D5-9C22-4D85-B748-290D15FF484C}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{CD46329B-D135-4594-9A70-55D3480F8FEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{CD46329B-D135-4594-9A70-55D3480F8FEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{CD46329B-D135-4594-9A70-55D3480F8FEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{CD46329B-D135-4594-9A70-55D3480F8FEE}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
{0F4B3B3C-E8C9-4CDC-A247-5029D777DFC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{0F4B3B3C-E8C9-4CDC-A247-5029D777DFC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{0F4B3B3C-E8C9-4CDC-A247-5029D777DFC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{0F4B3B3C-E8C9-4CDC-A247-5029D777DFC2}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="Build/Build Script.csproj" />
|
||||||
|
<Project Path="MindWork AI Studio/MindWork AI Studio.csproj" />
|
||||||
|
<Project Path="SharedTools/SharedTools.csproj" />
|
||||||
|
<Project Path="SourceCodeRules/SourceCodeRules/SourceCodeRules.csproj" />
|
||||||
|
<Project Path="SourceGeneratedMappings/SourceGeneratedMappings.csproj" />
|
||||||
|
<Project Path="Tests/Tests.csproj" />
|
||||||
|
<Project Path="TrimSmokeTest/TrimSmokeTest.csproj" />
|
||||||
|
</Solution>
|
||||||
File renamed without changes.
@@ -12301,9 +12301,6 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1069211263"] = "The request
|
|||||||
-- The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region.
|
-- The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1133173666"] = "The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region."
|
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1133173666"] = "The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region."
|
||||||
|
|
||||||
-- Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1487597412"] = "Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'"
|
|
||||||
|
|
||||||
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"
|
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors>Thorsten Sommer</Authors>
|
<Authors>Thorsten Sommer</Authors>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<WarningsAsErrors>CS8600;CS8602;CS8603;CS9057</WarningsAsErrors> <!-- CS9057: fail instead of silently skipping an analyzer which needs a newer compiler than the SDK's -->
|
<WarningsAsErrors>CS8600;CS8602;CS8603;CS9057</WarningsAsErrors> <!-- CS9057: fail instead of silently skipping an analyzer which needs a newer compiler than the SDK's -->
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
@@ -45,6 +45,13 @@
|
|||||||
-->
|
-->
|
||||||
<NoWarn>IL2026, CS8974</NoWarn>
|
<NoWarn>IL2026, CS8974</NoWarn>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Since .NET 10, the scripts of Blazor come from a package, which the release embeds: see the
|
||||||
|
EmbedBlazorScript target. The SDK adds that package by itself when it spots Razor files; we
|
||||||
|
ask for it here, so that the release does not hinge on that guess.
|
||||||
|
-->
|
||||||
|
<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Import Project="..\Trimming.props" />
|
<Import Project="..\Trimming.props" />
|
||||||
@@ -71,22 +78,20 @@
|
|||||||
<PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
|
<PackageReference Include="DocumentFormat.OpenXml" Version="3.5.1" />
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||||
<PackageReference Include="MailKit" Version="4.18.2" />
|
<PackageReference Include="MailKit" Version="4.18.2" />
|
||||||
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="9.0.22" />
|
<PackageReference Include="Microsoft.Data.Sqlite.Core" Version="10.0.14" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.22" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.14" />
|
||||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.22" />
|
|
||||||
|
<!--
|
||||||
|
Its assembly ships with ASP.NET Core, but the package also brings the build logic which writes the
|
||||||
|
manifest of the embedded files. Without that manifest, the release serves no static files at all, and
|
||||||
|
WebRootFiles finds neither the changelogs nor the ERI specifications in any build.
|
||||||
|
-->
|
||||||
|
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="10.0.14" />
|
||||||
<PackageReference Include="MudBlazor" Version="9.12.0" />
|
<PackageReference Include="MudBlazor" Version="9.12.0" />
|
||||||
<PackageReference Include="MudBlazor.Markdown" Version="9.0.0" />
|
<PackageReference Include="MudBlazor.Markdown" Version="9.0.0" />
|
||||||
<PackageReference Include="ReverseMarkdown" Version="5.0.0" />
|
<PackageReference Include="ReverseMarkdown" Version="5.0.0" />
|
||||||
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
|
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
|
||||||
<PackageReference Include="LuaCSharp" Version="0.5.8" />
|
<PackageReference Include="LuaCSharp" Version="0.5.8" />
|
||||||
|
|
||||||
<!--
|
|
||||||
Not used by AI Studio directly: MailKit pulls it in through MimeKit, which asks for 10.0.0. That
|
|
||||||
version is affected by CVE-2026-70162 and CVE-2026-84008 (GHSA-2fmv-rv2c-59x7, GHSA-846f-f764-76mx),
|
|
||||||
fixed in 10.0.13. The package wins over the older copy within the .NET runtime, so upgrading .NET
|
|
||||||
alone does not help. Remove this reference once MimeKit itself asks for 10.0.13 or newer.
|
|
||||||
-->
|
|
||||||
<PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.13" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -117,13 +122,26 @@
|
|||||||
<ReadLinesFromFile File="../../metadata.txt">
|
<ReadLinesFromFile File="../../metadata.txt">
|
||||||
<Output TaskParameter="Lines" PropertyName="Metadata" />
|
<Output TaskParameter="Lines" PropertyName="Metadata" />
|
||||||
</ReadLinesFromFile>
|
</ReadLinesFromFile>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
metadata.txt names the .NET SDK which the pipeline pins, but a local build may use another
|
||||||
|
one. Hence, we take the SDK which builds right now. Its .version file starts with the commit
|
||||||
|
it was built from. The .NET runtime is not recorded at all: the app asks for it while it runs.
|
||||||
|
-->
|
||||||
|
<PropertyGroup>
|
||||||
|
<DotnetSdkVersionFile>$(NetCoreRoot)sdk/$(NETCoreSdkVersion)/.version</DotnetSdkVersionFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ReadLinesFromFile File="$(DotnetSdkVersionFile)" Condition="Exists('$(DotnetSdkVersionFile)')">
|
||||||
|
<Output TaskParameter="Lines" PropertyName="DotnetSdkVersionLines" />
|
||||||
|
</ReadLinesFromFile>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<MetaVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 0 ])</MetaVersion>
|
<MetaVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 0 ])</MetaVersion>
|
||||||
<MetaBuildTime>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 1 ])</MetaBuildTime>
|
<MetaBuildTime>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 1 ])</MetaBuildTime>
|
||||||
<MetaBuild>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 2 ])</MetaBuild>
|
<MetaBuild>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 2 ])</MetaBuild>
|
||||||
<MetaDotnetSdkVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 3 ])</MetaDotnetSdkVersion>
|
<MetaDotnetSdkVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 3 ])</MetaDotnetSdkVersion>
|
||||||
<MetaDotnetVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 4 ])</MetaDotnetVersion>
|
<MetaDotnetSdkVersion Condition="'$(DotnetSdkVersionLines)' != ''">$(NETCoreSdkVersion) (commit $([System.String]::Copy( $(DotnetSdkVersionLines) ).Split( ';' )[ 0 ].Substring( 0, 10 )))</MetaDotnetSdkVersion>
|
||||||
<MetaRustVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 5 ])</MetaRustVersion>
|
<MetaRustVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 5 ])</MetaRustVersion>
|
||||||
<MetaMudBlazorVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 6 ])</MetaMudBlazorVersion>
|
<MetaMudBlazorVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 6 ])</MetaMudBlazorVersion>
|
||||||
<MetaTauriVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 7 ])</MetaTauriVersion>
|
<MetaTauriVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 7 ])</MetaTauriVersion>
|
||||||
@@ -152,11 +170,10 @@
|
|||||||
<_Parameter3>$(MetaBuild)</_Parameter3>
|
<_Parameter3>$(MetaBuild)</_Parameter3>
|
||||||
<_Parameter3_TypeName>System.UInt32</_Parameter3_TypeName>
|
<_Parameter3_TypeName>System.UInt32</_Parameter3_TypeName>
|
||||||
<_Parameter4>$(MetaDotnetSdkVersion)</_Parameter4>
|
<_Parameter4>$(MetaDotnetSdkVersion)</_Parameter4>
|
||||||
<_Parameter5>$(MetaDotnetVersion)</_Parameter5>
|
<_Parameter5>$(MetaRustVersion)</_Parameter5>
|
||||||
<_Parameter6>$(MetaRustVersion)</_Parameter6>
|
<_Parameter6>$(MetaMudBlazorVersion)</_Parameter6>
|
||||||
<_Parameter7>$(MetaMudBlazorVersion)</_Parameter7>
|
<_Parameter7>$(MetaTauriVersion)</_Parameter7>
|
||||||
<_Parameter8>$(MetaTauriVersion)</_Parameter8>
|
<_Parameter8>$(MetaAppCommitHash)</_Parameter8>
|
||||||
<_Parameter9>$(MetaAppCommitHash)</_Parameter9>
|
|
||||||
</AssemblyAttribute>
|
</AssemblyAttribute>
|
||||||
<AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataArchitecture">
|
<AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataArchitecture">
|
||||||
<_Parameter1>$(MetaArchitecture)</_Parameter1>
|
<_Parameter1>$(MetaArchitecture)</_Parameter1>
|
||||||
@@ -171,6 +188,18 @@
|
|||||||
|
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Since .NET 10, NuGet prunes a package whose assemblies ship with the shared framework, and with it the
|
||||||
|
package's build logic. Microsoft.Extensions.FileProviders.Embedded must not go this way, see its reference
|
||||||
|
above. .NET 10.0.0 already leaves it out of the pruning (dotnet/aspnetcore#63719); this target keeps it
|
||||||
|
even if a later version of .NET prunes it again. It is the workaround Microsoft gives in that issue.
|
||||||
|
-->
|
||||||
|
<Target Name="_PreserveFileProvidersEmbeddedPackageReference" AfterTargets="AddPrunePackageReferences">
|
||||||
|
<ItemGroup>
|
||||||
|
<PrunePackageReference Remove="Microsoft.Extensions.FileProviders.Embedded" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Scoped CSS (`.razor.css`) is bundled into `$(AssemblyName).styles.css`, which App.razor links.
|
Scoped CSS (`.razor.css`) is bundled into `$(AssemblyName).styles.css`, which App.razor links.
|
||||||
That bundle is generated at build time, so it never lives in the source tree's wwwroot. The
|
That bundle is generated at build time, so it never lives in the source tree's wwwroot. The
|
||||||
@@ -197,4 +226,28 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
`_framework/blazor.web.js` makes the UI interactive, and App.razor loads it. Up to .NET 9, the shared
|
||||||
|
framework served it. Since .NET 10, it is a static web asset of the package Microsoft.AspNetCore.App.Internal.Assets,
|
||||||
|
which names its folder in BlazorFrameworkStaticWebAssetRoot. Like the scoped CSS bundle above, the release
|
||||||
|
would therefore lack it: the window would show the app, but nothing in it would react. Hence: embed it
|
||||||
|
before the manifest is written.
|
||||||
|
-->
|
||||||
|
<Target Name="EmbedBlazorScript" BeforeTargets="_CalculateEmbeddedFilesManifestInputs">
|
||||||
|
<PropertyGroup>
|
||||||
|
<BlazorScriptToEmbed>$(BlazorFrameworkStaticWebAssetRoot)\blazor.web.js</BlazorScriptToEmbed>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Fail loudly instead of shipping a release without the script: a missing file here means the
|
||||||
|
package moved it, or the SDK no longer adds the package.
|
||||||
|
-->
|
||||||
|
<Error Condition="'$(BlazorFrameworkStaticWebAssetRoot)' == '' Or !Exists('$(BlazorScriptToEmbed)')"
|
||||||
|
Text="The script of Blazor was not found at '$(BlazorScriptToEmbed)'. Without it, the release starts with a window which does not react at all. Check where .NET keeps blazor.web.js and adjust the EmbedBlazorScript target." />
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="$(BlazorScriptToEmbed)" Link="wwwroot\_framework\blazor.web.js" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Target>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -89,7 +89,7 @@ public partial class Information : MSGComponentBase
|
|||||||
|
|
||||||
private string VersionRust => $"{T("Used Rust compiler")}: v{META_DATA.RustVersion}";
|
private string VersionRust => $"{T("Used Rust compiler")}: v{META_DATA.RustVersion}";
|
||||||
|
|
||||||
private string VersionDotnetRuntime => $"{T("Used .NET runtime")}: v{META_DATA.DotnetVersion}";
|
private string VersionDotnetRuntime => $"{T("Used .NET runtime")}: v{DotnetRuntimeVersion.Current}";
|
||||||
|
|
||||||
private string VersionDotnetSdk => $"{T("Used .NET SDK")}: v{META_DATA.DotnetSdkVersion}";
|
private string VersionDotnetSdk => $"{T("Used .NET SDK")}: v{META_DATA.DotnetSdkVersion}";
|
||||||
|
|
||||||
|
|||||||
-3
@@ -12303,9 +12303,6 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1069211263"] = "Die Anfrage
|
|||||||
-- The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region.
|
-- The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1133173666"] = "Der Anbieter „{0}“ hat die Anfrage abgelehnt. Möglicherweise ist Ihr Konto nicht für die Nutzung des ausgewählten Modells berechtigt, oder der Anbieter ist in Ihrer Region nicht verfügbar."
|
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1133173666"] = "Der Anbieter „{0}“ hat die Anfrage abgelehnt. Möglicherweise ist Ihr Konto nicht für die Nutzung des ausgewählten Modells berechtigt, oder der Anbieter ist in Ihrer Region nicht verfügbar."
|
||||||
|
|
||||||
-- Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1487597412"] = "Beim Versuch, die Antwort des LLM-Anbieters „{0}“ zu streamen, sind Probleme aufgetreten. Die Meldung lautet: „{1}“"
|
|
||||||
|
|
||||||
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Der Versuch, die Antwort des LLM-Anbieters „{0}“ zu streamen, ist fehlgeschlagen. Der Stream konnte nicht gelesen werden. Die Meldung lautet: „{1}“"
|
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Der Versuch, die Antwort des LLM-Anbieters „{0}“ zu streamen, ist fehlgeschlagen. Der Stream konnte nicht gelesen werden. Die Meldung lautet: „{1}“"
|
||||||
|
|
||||||
|
|||||||
-3
@@ -12303,9 +12303,6 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1069211263"] = "The request
|
|||||||
-- The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region.
|
-- The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region.
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1133173666"] = "The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region."
|
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1133173666"] = "The provider '{0}' refused the request. Your account might not be allowed to use the selected model, or the provider might not serve your region."
|
||||||
|
|
||||||
-- Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'
|
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1487597412"] = "Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'"
|
|
||||||
|
|
||||||
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
||||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"
|
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"
|
||||||
|
|
||||||
|
|||||||
@@ -1007,22 +1007,12 @@ public abstract class BaseProvider : IProvider, ISecretId
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// Read the stream, line by line:
|
// Read the stream, line by line, until it ends. We never ask the reader whether the
|
||||||
|
// stream ended: that would wait for the next data without any timeout, and it would
|
||||||
|
// hold a thread meanwhile. Reading the next line tells the same, see below.
|
||||||
//
|
//
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
try
|
|
||||||
{
|
|
||||||
if(streamReader.EndOfStream)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Stream, string.Format(TB("Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'"), this.InstanceName, e.Message)));
|
|
||||||
this.logger.LogWarning(e, "Failed to read the end-of-stream state from {ProviderName} '{ProviderInstanceName}': {ErrorMessage}", providerName, this.InstanceName, e.Message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the token is canceled:
|
// Check if the token is canceled:
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -1058,13 +1048,14 @@ public abstract class BaseProvider : IProvider, ISecretId
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The stream ended:
|
||||||
if (line is null)
|
if (line is null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Skip empty lines:
|
// Skip empty lines:
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (this.TryCreateProviderRequestExceptionFromStreamLine(providerName, line, out var providerRequestException))
|
if (this.TryCreateProviderRequestExceptionFromStreamLine(providerName, line, out var providerRequestException))
|
||||||
throw providerRequestException;
|
throw providerRequestException;
|
||||||
|
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ public sealed class Encryption(ILogger<Encryption> logger, byte[] secretPassword
|
|||||||
// Derive key and iv vector: the operations take several seconds. Thus, using a task:
|
// Derive key and iv vector: the operations take several seconds. Thus, using a task:
|
||||||
await Task.Run(() =>
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
using var keyVectorObj = new Rfc2898DeriveBytes(secretPassword, secretKeySalt, ITERATIONS, HashAlgorithmName.SHA512);
|
// The max valid key length = 256 bit = 32 bytes, and the only valid block size = 128 bit = 16 bytes:
|
||||||
var keyBytes = keyVectorObj.GetBytes(32); // the max valid key length = 256 bit = 32 bytes
|
var keyVectorBytes = Rfc2898DeriveBytes.Pbkdf2(secretPassword, secretKeySalt, ITERATIONS, HashAlgorithmName.SHA512, this.key.Length + this.iv.Length);
|
||||||
var ivBytes = keyVectorObj.GetBytes(16); // the only valid block size = 128 bit = 16 bytes
|
|
||||||
|
Array.Copy(keyVectorBytes, 0, this.key, 0, this.key.Length);
|
||||||
Array.Copy(keyBytes, this.key, this.key.Length);
|
Array.Copy(keyVectorBytes, this.key.Length, this.iv, 0, this.iv.Length);
|
||||||
Array.Copy(ivBytes, this.iv, this.iv.Length);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var initDuration = stopwatch.Elapsed;
|
var initDuration = stopwatch.Elapsed;
|
||||||
|
|||||||
@@ -292,11 +292,11 @@ public sealed class EnterpriseEncryption
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private Aes CreateAesV1(byte[] salt)
|
private Aes CreateAesV1(byte[] salt)
|
||||||
{
|
{
|
||||||
// Derive key and IV using PBKDF2:
|
// Derive key and IV using PBKDF2, the first 32 bytes for AES-256 and the last 16 for its block size:
|
||||||
using var keyDerivation = new Rfc2898DeriveBytes(this.secretKey!, salt, V1_ITERATIONS, HashAlgorithmName.SHA512);
|
var keyAndIv = Rfc2898DeriveBytes.Pbkdf2(this.secretKey!, salt, V1_ITERATIONS, HashAlgorithmName.SHA512, 48);
|
||||||
var aes = Aes.Create();
|
var aes = Aes.Create();
|
||||||
aes.Key = keyDerivation.GetBytes(32); // AES-256
|
aes.Key = keyAndIv[..32];
|
||||||
aes.IV = keyDerivation.GetBytes(16); // AES block size
|
aes.IV = keyAndIv[32..];
|
||||||
aes.Mode = CipherMode.CBC;
|
aes.Mode = CipherMode.CBC;
|
||||||
aes.Padding = PaddingMode.PKCS7;
|
aes.Padding = PaddingMode.PKCS7;
|
||||||
return aes;
|
return aes;
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ public static class ExternalHttpClientTimeout
|
|||||||
customChain.ChainPolicy.CustomTrustStore.AddRange(customRootCertificateCache.Certificates);
|
customChain.ChainPolicy.CustomTrustStore.AddRange(customRootCertificateCache.Certificates);
|
||||||
customChain.ChainPolicy.ApplicationPolicy.Add(new Oid(TLS_SERVER_AUTHENTICATION_EKU_OID));
|
customChain.ChainPolicy.ApplicationPolicy.Add(new Oid(TLS_SERVER_AUTHENTICATION_EKU_OID));
|
||||||
|
|
||||||
// Match the .NET 9 HttpClient default used for the initial system-trust validation.
|
// Match the HttpClient default used for the initial system-trust validation, which .NET 10 kept.
|
||||||
// Hostname, signature, validity, EKU, and root trust checks remain enabled.
|
// Hostname, signature, validity, EKU, and root trust checks remain enabled.
|
||||||
customChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
customChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace AIStudio.Tools.Metadata;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The .NET runtime which runs the app.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Asked while the app runs instead of taken from metadata.txt: a release brings its own runtime,
|
||||||
|
/// and a development build runs on whichever runtime the developer installed.
|
||||||
|
/// </remarks>
|
||||||
|
public static class DotnetRuntimeVersion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How many characters of the commit we show, as metadata.txt does.
|
||||||
|
/// </summary>
|
||||||
|
private const int COMMIT_LENGTH = 10;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the version of the runtime together with the commit it was built from, such as
|
||||||
|
/// <c>10.0.14 (commit 0427b9abbe)</c>.
|
||||||
|
/// </summary>
|
||||||
|
public static string Current { get; } = Describe(Environment.Version, typeof(object).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Describes a version of the runtime together with the commit which the informational version
|
||||||
|
/// of its core library names after a plus sign, such as <c>10.0.14+0427b9abbead3f…</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version">The version of the runtime.</param>
|
||||||
|
/// <param name="informationalVersion">The informational version of the core library, if any.</param>
|
||||||
|
/// <returns>The version with its commit, or the version alone when the commit is unknown.</returns>
|
||||||
|
internal static string Describe(Version version, string? informationalVersion)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(informationalVersion))
|
||||||
|
return version.ToString();
|
||||||
|
|
||||||
|
var plusIndex = informationalVersion.IndexOf('+');
|
||||||
|
var commit = plusIndex < 0 ? string.Empty : informationalVersion[(plusIndex + 1)..];
|
||||||
|
return commit.Length < COMMIT_LENGTH ? version.ToString() : $"{version} (commit {commit[..COMMIT_LENGTH]})";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,6 @@ public class MetaDataAttribute(
|
|||||||
string buildTime,
|
string buildTime,
|
||||||
uint buildNum,
|
uint buildNum,
|
||||||
string dotnetSdkVersion,
|
string dotnetSdkVersion,
|
||||||
string dotnetVersion,
|
|
||||||
string rustVersion,
|
string rustVersion,
|
||||||
string mudBlazorVersion,
|
string mudBlazorVersion,
|
||||||
string tauriVersion,
|
string tauriVersion,
|
||||||
@@ -20,8 +19,9 @@ public class MetaDataAttribute(
|
|||||||
|
|
||||||
public uint BuildNum => buildNum;
|
public uint BuildNum => buildNum;
|
||||||
|
|
||||||
public string DotnetVersion => dotnetVersion;
|
/// <summary>
|
||||||
|
/// Gets the .NET SDK which built the app. The .NET runtime which runs it is <see cref="DotnetRuntimeVersion.Current"/>.
|
||||||
|
/// </summary>
|
||||||
public string DotnetSdkVersion => dotnetSdkVersion;
|
public string DotnetSdkVersion => dotnetSdkVersion;
|
||||||
|
|
||||||
public string RustVersion => rustVersion;
|
public string RustVersion => rustVersion;
|
||||||
|
|||||||
@@ -549,12 +549,22 @@ public sealed partial class DataSourceEmbeddingService(SettingsManager settingsM
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
public override Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
// Changes to the embedding settings which do not pass through the queue, cf. ProcessMessage:
|
//
|
||||||
|
// Changes to the embedding settings which do not pass through the queue, cf. ProcessMessage.
|
||||||
|
// We listen for them while the app starts: since .NET 10, ExecuteAsync runs as a task of its
|
||||||
|
// own from its very first line, and a message sent before that task got its turn would go
|
||||||
|
// unnoticed.
|
||||||
|
//
|
||||||
MessageBus.INSTANCE.RegisterComponent(this);
|
MessageBus.INSTANCE.RegisterComponent(this);
|
||||||
this.ApplyFilters([], [Event.PLUGINS_RELOADED, Event.CONFIGURATION_CHANGED]);
|
this.ApplyFilters([], [Event.PLUGINS_RELOADED, Event.CONFIGURATION_CHANGED]);
|
||||||
|
|
||||||
|
return base.StartAsync(cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
await this.WaitForInitialSettingsAndBootstrapAsync(stoppingToken);
|
await this.WaitForInitialSettingsAndBootstrapAsync(stoppingToken);
|
||||||
|
|
||||||
while (!stoppingToken.IsCancellationRequested)
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
|||||||
@@ -27,12 +27,9 @@ public partial class RustService
|
|||||||
// Read events line by line:
|
// Read events line by line:
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
// Read until the end of the stream or cancellation:
|
// Read the next line of JSON until the end of the stream or cancellation:
|
||||||
while(!reader.EndOfStream && !stopToken.IsCancellationRequested)
|
while(!stopToken.IsCancellationRequested && await reader.ReadLineAsync(stopToken) is { } line)
|
||||||
{
|
{
|
||||||
// Read the next line of JSON from the stream:
|
|
||||||
var line = await reader.ReadLineAsync(stopToken);
|
|
||||||
|
|
||||||
// Skip empty lines:
|
// Skip empty lines:
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -352,9 +352,8 @@ public sealed partial class RustService
|
|||||||
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
await using var stream = await response.Content.ReadAsStreamAsync(token);
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
|
|
||||||
while (!reader.EndOfStream && !token.IsCancellationRequested)
|
while (!token.IsCancellationRequested && await reader.ReadLineAsync(token) is { } line)
|
||||||
{
|
{
|
||||||
var line = await reader.ReadLineAsync(token);
|
|
||||||
if (string.IsNullOrWhiteSpace(line))
|
if (string.IsNullOrWhiteSpace(line))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"net9.0": {
|
"net10.0": {
|
||||||
"CodeBeam.MudBlazor.Extensions": {
|
"CodeBeam.MudBlazor.Extensions": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.2.0, )",
|
"requested": "[9.2.0, )",
|
||||||
@@ -42,60 +42,53 @@
|
|||||||
"resolved": "4.18.2",
|
"resolved": "4.18.2",
|
||||||
"contentHash": "ofqddpSbdrD00NqTD0ZyVfKvrl/ZZ70VFFEQ+DuR8r8CdbY3U02/sdb0CSQBNUqJifsXfVHIwRe1ToR2MvJJkw==",
|
"contentHash": "ofqddpSbdrD00NqTD0ZyVfKvrl/ZZ70VFFEQ+DuR8r8CdbY3U02/sdb0CSQBNUqJifsXfVHIwRe1ToR2MvJJkw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"MimeKit": "4.18.2",
|
"MimeKit": "4.18.2"
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Microsoft.AspNetCore.App.Internal.Assets": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[10.0.14, )",
|
||||||
|
"resolved": "10.0.14",
|
||||||
|
"contentHash": "T1IOYbID1msKjQEh684HebnN+Xnq/pocMXDukOMZtzzPTf9c4/30+/JndM/1txrqwuSYqWvLiAQWzu4U93cshw=="
|
||||||
|
},
|
||||||
"Microsoft.Data.Sqlite.Core": {
|
"Microsoft.Data.Sqlite.Core": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.22, )",
|
"requested": "[10.0.14, )",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "ivq0VMiRaR2tvwcZYbzJUMrQtbBSLDkZyTNM8FsBv56Ne664XZR2XlhDF0RuAsCDt5lU9yKRs0WHOuDbROv2JQ==",
|
"contentHash": "1p9rO3Q2X/L/9YrYog3vaDzi8u+WZfJCOkDYoHqmofqYP0DLPLPyZcS2Rnh/79JXAodgbs1AgsaNwLpIvC3Xew==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"SQLitePCLRaw.core": "2.1.13"
|
"SQLitePCLRaw.core": "2.1.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.EntityFrameworkCore.Sqlite": {
|
"Microsoft.EntityFrameworkCore.Sqlite": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.22, )",
|
"requested": "[10.0.14, )",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "yj54/8fLJC0uI7t5m7uUid3nOYDl7J+G3Hf6MSz+ULyeguA1fxZw3F1u3wMnEBEaH0i8d2hCAMdxanDplIUfvw==",
|
"contentHash": "lxrwnVwvlIfQD4IF4WUVupE6Qxfj3MfFftDVwo7tsGAWCKLL8Ttb8UjIt+gE4e9nRlH7EgdC6TrsArFGnNYREw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.EntityFrameworkCore.Sqlite.Core": "9.0.22",
|
"Microsoft.EntityFrameworkCore.Sqlite.Core": "10.0.14",
|
||||||
"Microsoft.Extensions.Caching.Memory": "9.0.22",
|
"Microsoft.Extensions.DependencyModel": "10.0.14",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.22",
|
|
||||||
"Microsoft.Extensions.DependencyModel": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Logging": "9.0.22",
|
|
||||||
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.13",
|
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.13",
|
||||||
"SQLitePCLRaw.core": "2.1.13",
|
"SQLitePCLRaw.core": "2.1.13"
|
||||||
"System.Text.Json": "9.0.22"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.FileProviders.Embedded": {
|
"Microsoft.Extensions.FileProviders.Embedded": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.22, )",
|
"requested": "[10.0.14, )",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "8pjPIPzG5OGIBG65TSUExMZeMeFpCDVJPtNzYtcJyNPCqgOI/skfKq5ZEca1VmswL2CQ7oxEYiCFNO2LhAJ4uA==",
|
"contentHash": "v+KNMXj2RktQ690ijlFYniH8gggO+EVMjCZSou+NcpI2mKNAT27Dgn2UfiJKbwQ84v2Avp6Mwceqog8gvQn5Cw=="
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Microsoft.NET.ILLink.Tasks": {
|
"Microsoft.NET.ILLink.Tasks": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.0.22, )",
|
"requested": "[10.0.14, )",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "S61AsICGqZii54jXMY56s6xvu3PcIe9VPeBxcTAWwNt5P21l2Arcy36XfATNvZAEqoAH7OfGNzng+aoXkqhsxQ=="
|
"contentHash": "YlTamXppEQls6DYWt7RdUuwWDb0lyw/vXCaGeQpLCpsLmfwGuCkOz/vIGgMTV7ECsJcavkCOkxZXv9s8rdAHkQ=="
|
||||||
},
|
},
|
||||||
"MudBlazor": {
|
"MudBlazor": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[9.12.0, )",
|
"requested": "[9.12.0, )",
|
||||||
"resolved": "9.12.0",
|
"resolved": "9.12.0",
|
||||||
"contentHash": "T6PQUCJe60UTSy3u1F8R2nuC06Cox6WEhon5GA2/AZNVI1WstlPSJZdGPyp6pHFpWdjPpla65piLpyJsbW+1rA==",
|
"contentHash": "T6PQUCJe60UTSy3u1F8R2nuC06Cox6WEhon5GA2/AZNVI1WstlPSJZdGPyp6pHFpWdjPpla65piLpyJsbW+1rA=="
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.AspNetCore.Components": "9.0.1",
|
|
||||||
"Microsoft.AspNetCore.Components.Web": "9.0.1",
|
|
||||||
"Microsoft.Extensions.Localization": "9.0.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"MudBlazor.Markdown": {
|
"MudBlazor.Markdown": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
@@ -126,16 +119,6 @@
|
|||||||
"SQLitePCLRaw.config.e_sqlite3": "3.0.5"
|
"SQLitePCLRaw.config.e_sqlite3": "3.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"System.Security.Cryptography.Pkcs": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.13, )",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Bcl.Cryptography": "10.0.13",
|
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"BouncyCastle.Cryptography": {
|
"BouncyCastle.Cryptography": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "2.7.0",
|
"resolved": "2.7.0",
|
||||||
@@ -146,7 +129,7 @@
|
|||||||
"resolved": "3.5.1",
|
"resolved": "3.5.1",
|
||||||
"contentHash": "U5txtc3ORno73xQx9Lf2gWzfaSZnZwKHfLkTAslhlew9lxe5XbUiCt0dY1fHeAf8yRqszUAe5i/+xLC9R/Xfsw==",
|
"contentHash": "U5txtc3ORno73xQx9Lf2gWzfaSZnZwKHfLkTAslhlew9lxe5XbUiCt0dY1fHeAf8yRqszUAe5i/+xLC9R/Xfsw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"System.IO.Packaging": "8.0.1"
|
"System.IO.Packaging": "10.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"LuaCSharp.Annotations": {
|
"LuaCSharp.Annotations": {
|
||||||
@@ -164,213 +147,48 @@
|
|||||||
"resolved": "0.45.0",
|
"resolved": "0.45.0",
|
||||||
"contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg=="
|
"contentHash": "ObNLcA1b+0lpNNoEg256g9faMeJZi35wZW0AnKJ4nGPJe+5qkwnV26kUvQTHuanFnSX9SdvPzOO41BVJ6XarAg=="
|
||||||
},
|
},
|
||||||
"Microsoft.AspNetCore.Authorization": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "WgLlLBlMczb2+QLNG6sM95OUZ0EBztz60k/N75tjIgpyu0SdpIfYytAmX/7JJAjRTZF0c/CrWaQV+SH9FuGsrA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.AspNetCore.Metadata": "9.0.1",
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
|
||||||
"Microsoft.Extensions.Options": "9.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Components": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "6pwfbQKNtvPkbF4tCGiAKGyt6BVpu58xAXz7u2YXcUKTNmNxrymbG1mEyMc0EPzVdnquDDqTyfXM3mC1EJycxQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.AspNetCore.Authorization": "9.0.1",
|
|
||||||
"Microsoft.AspNetCore.Components.Analyzers": "9.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Components.Analyzers": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "I8Rs4LXT5UQxM5Nin2+Oj8aSY2heszSZ3EyTLgt3mxmfiRPrVO7D8NNSsf1voI2Gb0qFJceof/J5c9E+nfNuHw=="
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Components.Forms": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "KyULVU32bLz74LWDwPEwNUEllTehzWJuM7YAsz80rMKEzvR0K8cRjRzO0fnN/nfydMeLRRlbI0xj8wnEAymLVw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.AspNetCore.Components": "9.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Components.Web": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "LI0vjYEd9MaDZPDQxPCn4gGYDkEC5U9rp1nWZo7rPozJxgTG2zU3WERujxTi2LeAC2ZzdXlOVCrUyPQ55LZV2A==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.AspNetCore.Components": "9.0.1",
|
|
||||||
"Microsoft.AspNetCore.Components.Forms": "9.0.1",
|
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.1",
|
|
||||||
"Microsoft.Extensions.Primitives": "9.0.1",
|
|
||||||
"Microsoft.JSInterop": "9.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.AspNetCore.Metadata": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "EZnHifamF7IFEIyjAKMtJM3I/94OIe72i3P09v5oL0twmsmfQwal6Ni3m8lbB5mge3jWFhMozeW+rUdRSqnXRQ=="
|
|
||||||
},
|
|
||||||
"Microsoft.Bcl.Cryptography": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "6H3QqvjxTgCt4gxVVCJVjo35Zh/gzjw7NXidIx0ghZrgYYMUja/7JJ4g39ynE/OH5YBBbUhlmirGR/AuYpNIuA=="
|
|
||||||
},
|
|
||||||
"Microsoft.EntityFrameworkCore": {
|
"Microsoft.EntityFrameworkCore": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "wBYt52v8cQ5abaA8jR3m+LPV8N3ymhz0pTPMvwYck9VZ9ItcYIu130qEYdsinMNt9co+4n7SMZdI1dFjJHIvNQ==",
|
"contentHash": "vY3931PYPIDBbKovGHbACmpOWvURvKNyRXfCjMh5yLW+FH6vLWeqUu2xzSCRo4Oz4tgx9GYpd0JlV5LDyAoQdg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.EntityFrameworkCore.Abstractions": "9.0.22",
|
"Microsoft.EntityFrameworkCore.Abstractions": "10.0.14",
|
||||||
"Microsoft.EntityFrameworkCore.Analyzers": "9.0.22",
|
"Microsoft.EntityFrameworkCore.Analyzers": "10.0.14"
|
||||||
"Microsoft.Extensions.Caching.Memory": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Logging": "9.0.22"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.EntityFrameworkCore.Abstractions": {
|
"Microsoft.EntityFrameworkCore.Abstractions": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "H1OiSYqSi4q9LnYfr4H6qkMOiS+bMSylLkbTcjlotI0aRojjDPHS+P2JAIn+kvzd8+wcVbzD1Jw0JexBAjiK2Q=="
|
"contentHash": "/SaznoTTFfKzxYEbCPAwwKBR3XT92DZgH4jJ3/cqs2skP3o2D+TL6i9Aaj+8Azwtu6/sRd0ve4P2mN12nfVmCw=="
|
||||||
},
|
},
|
||||||
"Microsoft.EntityFrameworkCore.Analyzers": {
|
"Microsoft.EntityFrameworkCore.Analyzers": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "wtYr03ZnX3a7x92WE0yELwyhN2EooJ7BTlY4N2gBPaVBsg2XwjEKyyGBLbkhc9fgKaQ6ed1fEgnHmZ9YCfTlLQ=="
|
"contentHash": "gaANlGNTxkgODX2eeVT8jjd3AmlFilQV4DFf8qoW30+wUCm2o4N7nEHtZvnHIwfhaJni2DqcjE47Au7tC0Q5jA=="
|
||||||
},
|
},
|
||||||
"Microsoft.EntityFrameworkCore.Relational": {
|
"Microsoft.EntityFrameworkCore.Relational": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "BwTl8180y2ct3po1+5MYk72HY0qZsbB4nqj/bbaHi9stz23Sxsf3xcG0CtASjm0T5UzBPVLAj9CNinuWfiLx3Q==",
|
"contentHash": "Ut0kgtfoKX9QElQVEXSEAgdQc79jISWStVWSOhfPDDHNSKMwFzSXG3gtdYFTAbsBoV+8NtXdVaj4xwdqeky8UA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.EntityFrameworkCore": "9.0.22",
|
"Microsoft.EntityFrameworkCore": "10.0.14"
|
||||||
"Microsoft.Extensions.Caching.Memory": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Logging": "9.0.22"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.EntityFrameworkCore.Sqlite.Core": {
|
"Microsoft.EntityFrameworkCore.Sqlite.Core": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "AIvmI99uWB47b82svKMcevpNLhX+2VBPf/l9VJUGP+8PLBle8jwPguGwzy3MdjsM2AfVE/QoJK7mQ2Xd8e0vJw==",
|
"contentHash": "08VGlMQ9WbK869as9Ho+VZQbM5Qu6zdorgeh6ShVwQgLYgfUBiaIm02T7spfW7Xc1MBHCvclwPgGWpvx5BYGXQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.Data.Sqlite.Core": "9.0.22",
|
"Microsoft.Data.Sqlite.Core": "10.0.14",
|
||||||
"Microsoft.EntityFrameworkCore.Relational": "9.0.22",
|
"Microsoft.EntityFrameworkCore.Relational": "10.0.14",
|
||||||
"Microsoft.Extensions.Caching.Memory": "9.0.22",
|
"Microsoft.Extensions.DependencyModel": "10.0.14",
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": "9.0.22",
|
"SQLitePCLRaw.core": "2.1.13"
|
||||||
"Microsoft.Extensions.DependencyModel": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Logging": "9.0.22",
|
|
||||||
"SQLitePCLRaw.core": "2.1.13",
|
|
||||||
"System.Text.Json": "9.0.22"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Microsoft.Extensions.Caching.Abstractions": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "hQo0hI5IDADpxo3wiGJa6A2T5InBCWPAY65J2QZAPoy+5e9MDgpRGERei7DABAPd58hnjwnpFVXa/NUjcygqmw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.Primitives": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Caching.Memory": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "V6Kzo5peqxfE5Dwsgt9ngK5hnJwI2F/QXqh2HR2naUCx1j8sGEs/wBhUib83PiaRi5Oc8Iux1nreLVwmfResDA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.Caching.Abstractions": "9.0.22",
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Options": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Primitives": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Configuration.Abstractions": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "JTG92S+wiC49A3edjZTOk5/HMaFqaMn2/Y3RAAt9tK0Aoekfvp6ETJz3wOVsRCFqmAPX84vgcKtu/y920WBAGA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.Primitives": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.DependencyInjection": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "fG8YKmh9xZbVSaKEHDQfG/BEToPezfIw9/EfIpIRVGVftJyFpp7KNqaiZmKDm2apBfjRA0iSdeyxNS9Kc842Dw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "SFzDXodpxwCTJQ9IsmqhCsh8fAElRqGtuOqwQACA7pKBj277g+A8q8zzZebk0Q+MKrHX+WFT3/i64n4BdhsMpQ=="
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.DependencyModel": {
|
"Microsoft.Extensions.DependencyModel": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.14",
|
||||||
"contentHash": "sqIODKUAUaj/im0LcKSW59JY74vMnxIVDFG61ASH6O4q5LaazjP2XRDqOzjou5bjhI1AW4F7oyOz01NsmH194A=="
|
"contentHash": "N8OcqM0370wSXMJ3ZxO66rSElHrIgxvpfUn0vV7zRj7NvKo0nfwgEoTmC3Yz3860o60IaQ4KIPyeLIGX9EeNWA=="
|
||||||
},
|
|
||||||
"Microsoft.Extensions.FileProviders.Abstractions": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "HhBR/vC5PwIEkvNqOcBR0+VRYBXR5zsv8Ddsk4qj9Jh29bE+LCxd1cPx7rdK0PIANVT82vAmJcXUT7eFw7CwbA==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.Primitives": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Localization": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "UgvX4Yb2T3tEsKT30ktZr0H7kTRPapCgEH0bdTwxiEGSdA39/hAQMvvb+vgHpqmevDU5+puyI9ujRkmmbF946w==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1",
|
|
||||||
"Microsoft.Extensions.Localization.Abstractions": "9.0.1",
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.1",
|
|
||||||
"Microsoft.Extensions.Options": "9.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Localization.Abstractions": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "CABog43lyaZQMjmlktuImCy6zmAzRBaXqN81uPaMQjlp//ISDVYItZPh6KWpWRF4MY/B67X5oDc3JTUpfdocZw=="
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Logging": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "mYX+XnqcOr1Mffohw3y91BXlLV0df848jIbT2e/h4GZ5uQBJaBdo7VuX5mWJdnpu7roTUUUMYHjYR9u/O/XRqg==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.DependencyInjection": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Options": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Logging.Abstractions": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "REayp367aujZONmJbX6ctXSZAJjgovhhIy8qoHsY50y/nXhtpPPndsXgz7EHnbZef5eCIMG0VbTjFf3/sXs5aQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Options": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "HXvtEnW4d3LdZuA2T31tT7DMkhtXOGvncxNN6cqoQLV/HDtF/TYTW/8hVy+di/w/VerPOJ5c03nhfjxkHORwiw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.22",
|
|
||||||
"Microsoft.Extensions.Primitives": "9.0.22"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Microsoft.Extensions.Primitives": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.22",
|
|
||||||
"contentHash": "sna268GZylvTK/y/HJgjs1fqKBDG3Yj4nsCQQNiIhKKITlvcFbEaf65FTC754VXHu1PE1HoDvXblRwi7LU8CGw=="
|
|
||||||
},
|
|
||||||
"Microsoft.JSInterop": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "9.0.1",
|
|
||||||
"contentHash": "/xBwIfb0YoC2Muv6EsHjxpqZw2aKv94+i0g0FWZvqvGv3DeAy+8wipAuECVvKYEs2EIclRD41bjajHLoD6mTtw=="
|
|
||||||
},
|
},
|
||||||
"MimeKit": {
|
"MimeKit": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
@@ -407,125 +225,90 @@
|
|||||||
"SQLitePCLRaw.core": "3.0.5"
|
"SQLitePCLRaw.core": "3.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"System.Formats.Asn1": {
|
|
||||||
"type": "Transitive",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "uwKFGyJaUQ8xsf+wddv512+UIU1O4reOveom3y0gpQr86P1ReAKcQa/UdOZtg1etTIHgb01/grXp00tgeGBxqA=="
|
|
||||||
},
|
|
||||||
"System.IO.Packaging": {
|
"System.IO.Packaging": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "8.0.1",
|
"resolved": "10.0.2",
|
||||||
"contentHash": "KYkIOAvPexQOLDxPO2g0BVoWInnQhPpkFzRqvNrNrMhVT6kqhVr0zEb6KCHlptLFukxnZrjuMVAnxK7pOGUYrw=="
|
"contentHash": "JTpM4z0wpoIHHDvlCU27HsXo+zVnpWib94HXQpzzr+jc/P9NYf4w353AK4MXyGq/grm1mbLi7eXsOsDU8sGmNg=="
|
||||||
},
|
},
|
||||||
"System.Text.Json": {
|
"System.Security.Cryptography.Pkcs": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "9.0.22",
|
"resolved": "10.0.13",
|
||||||
"contentHash": "bP1LqWP1h1/J/XO69m5kdLF7M1HT6YWpM7UflcIKLLTvJep8HoS6I/F8FnV/uGhqmStLHY8PuiQAmjQg+qqqcg=="
|
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw=="
|
||||||
},
|
},
|
||||||
"sharedtools": {
|
"sharedtools": {
|
||||||
"type": "Project"
|
"type": "Project"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net9.0/linux-arm64": {
|
"net10.0/linux-arm64": {
|
||||||
"System.Security.Cryptography.Pkcs": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.13, )",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Bcl.Cryptography": "10.0.13",
|
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SQLite": {
|
"SQLite": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.53.4",
|
"resolved": "3.53.4",
|
||||||
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
||||||
|
},
|
||||||
|
"System.Security.Cryptography.Pkcs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.13",
|
||||||
|
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net9.0/linux-x64": {
|
"net10.0/linux-x64": {
|
||||||
"System.Security.Cryptography.Pkcs": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.13, )",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Bcl.Cryptography": "10.0.13",
|
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SQLite": {
|
"SQLite": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.53.4",
|
"resolved": "3.53.4",
|
||||||
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
||||||
|
},
|
||||||
|
"System.Security.Cryptography.Pkcs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.13",
|
||||||
|
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net9.0/osx-arm64": {
|
"net10.0/osx-arm64": {
|
||||||
"System.Security.Cryptography.Pkcs": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.13, )",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Bcl.Cryptography": "10.0.13",
|
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SQLite": {
|
"SQLite": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.53.4",
|
"resolved": "3.53.4",
|
||||||
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
||||||
|
},
|
||||||
|
"System.Security.Cryptography.Pkcs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.13",
|
||||||
|
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net9.0/osx-x64": {
|
"net10.0/osx-x64": {
|
||||||
"System.Security.Cryptography.Pkcs": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.13, )",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Bcl.Cryptography": "10.0.13",
|
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SQLite": {
|
"SQLite": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.53.4",
|
"resolved": "3.53.4",
|
||||||
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
||||||
|
},
|
||||||
|
"System.Security.Cryptography.Pkcs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.13",
|
||||||
|
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net9.0/win-arm64": {
|
"net10.0/win-arm64": {
|
||||||
"System.Security.Cryptography.Pkcs": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.13, )",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Bcl.Cryptography": "10.0.13",
|
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SQLite": {
|
"SQLite": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.53.4",
|
"resolved": "3.53.4",
|
||||||
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
||||||
|
},
|
||||||
|
"System.Security.Cryptography.Pkcs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.13",
|
||||||
|
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw=="
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"net9.0/win-x64": {
|
"net10.0/win-x64": {
|
||||||
"System.Security.Cryptography.Pkcs": {
|
|
||||||
"type": "Direct",
|
|
||||||
"requested": "[10.0.13, )",
|
|
||||||
"resolved": "10.0.13",
|
|
||||||
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw==",
|
|
||||||
"dependencies": {
|
|
||||||
"Microsoft.Bcl.Cryptography": "10.0.13",
|
|
||||||
"System.Formats.Asn1": "10.0.13"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"SQLite": {
|
"SQLite": {
|
||||||
"type": "Transitive",
|
"type": "Transitive",
|
||||||
"resolved": "3.53.4",
|
"resolved": "3.53.4",
|
||||||
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
"contentHash": "KN7jeWqgUPeBRe1FlcpZURzxomuKKEKHmBBQfg+Nx7NkY1LjKhzHvH+3ASkNvhayESE34nMBinL9CV21JfPRJw=="
|
||||||
|
},
|
||||||
|
"System.Security.Cryptography.Pkcs": {
|
||||||
|
"type": "Transitive",
|
||||||
|
"resolved": "10.0.13",
|
||||||
|
"contentHash": "a+EovgC1fBnwdlQ6cn3j4JFBfKqiFaFvoAhjisOoSvazLAcIznaBzPQP0oYnvccmeXfqUH1KebVZOxSeH6rMaw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,5 @@
|
|||||||
- Fixed chats being stored although you chose "Store chats manually" or "Disable workspaces". AI Studio now stores a chat only when you click "Save chat", or not at all while workspaces are disabled. In both modes, leaving a chat whose answer is still being written asks you first and stops that answer. Chats outside of workspaces that earlier versions stored this way appear among your disappearing chats once you use workspaces again.
|
- Fixed chats being stored although you chose "Store chats manually" or "Disable workspaces". AI Studio now stores a chat only when you click "Save chat", or not at all while workspaces are disabled. In both modes, leaving a chat whose answer is still being written asks you first and stops that answer. Chats outside of workspaces that earlier versions stored this way appear among your disappearing chats once you use workspaces again.
|
||||||
- Upgraded several libraries to improve security.
|
- Upgraded several libraries to improve security.
|
||||||
- Upgraded Tauri to v2.12.5.
|
- Upgraded Tauri to v2.12.5.
|
||||||
- Upgraded MudBlazor to v9.12.0.
|
- Upgraded MudBlazor to v9.12.0.
|
||||||
|
- Upgraded to .NET v10.0.14.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<LangVersion>latest</LangVersion>
|
<LangVersion>latest</LangVersion>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|||||||
@@ -20,13 +20,13 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<!--
|
<!--
|
||||||
Never above the Roslyn of the .NET SDK we build with (see metadata.txt): the 9.0.1xx SDKs ship
|
Never above the Roslyn of the .NET SDK we build with (see metadata.txt): the 10.0.1xx SDKs ship
|
||||||
Roslyn 4.12. Built against a newer one, the compiler skips these analyzers with CS9057. Raise
|
Roslyn 5.0. Built against a newer one, the compiler skips these analyzers with CS9057. Raise
|
||||||
both only together with the SDK feature band, with SourceGeneratedMappings, and with the Tests
|
both only together with the SDK feature band, with SourceGeneratedMappings, and with the Tests
|
||||||
project.
|
project.
|
||||||
-->
|
-->
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -21,11 +21,11 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<!--
|
<!--
|
||||||
Never above the Roslyn of the .NET SDK we build with (see metadata.txt): the 9.0.1xx SDKs ship
|
Never above the Roslyn of the .NET SDK we build with (see metadata.txt): the 10.0.1xx SDKs ship
|
||||||
Roslyn 4.12. Built against a newer one, the compiler skips this generator with CS9057, and the
|
Roslyn 5.0. Built against a newer one, the compiler skips this generator with CS9057, and the
|
||||||
generated namespaces such as AIStudio.Tools.PluginSystem.Assistants.Icons go missing. Raise it
|
generated namespaces such as AIStudio.Tools.PluginSystem.Assistants.Icons go missing. Raise it
|
||||||
only together with the SDK feature band, with SourceCodeRules, and with the Tests project.
|
only together with the SDK feature band, with SourceCodeRules, and with the Tests project.
|
||||||
-->
|
-->
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -26,12 +26,12 @@ public sealed class MissingInterpolationAnalyzerTests
|
|||||||
{
|
{
|
||||||
var reported = await AnalyzeAsync(expression);
|
var reported = await AnalyzeAsync(expression);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reported, Has.Count.EqualTo(1));
|
Assert.That(reported, Has.Count.EqualTo(1));
|
||||||
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0014"));
|
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0014"));
|
||||||
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain("\"{this.location}\""));
|
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain("\"{this.location}\""));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
{
|
{
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, PROJECT_FOLDER, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, PROJECT_FOLDER, RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "notes", "minutes.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "notes", "minutes.md")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "log.csv")), Is.False, "The log of a run lies in the output folder, so a file of this name in the input folder is a document.");
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "log.csv")), Is.False, "The log of a run lies in the output folder, so a file of this name in the input folder is a document.");
|
||||||
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -39,12 +39,12 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
{
|
{
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, DESKTOP, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, DESKTOP, RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "notes", "minutes.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "notes", "minutes.md")), Is.False);
|
||||||
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -53,13 +53,13 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
var resultsFolder = Path.Combine(INPUT_FOLDER, "ai-results");
|
var resultsFolder = Path.Combine(INPUT_FOLDER, "ai-results");
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, resultsFolder, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, resultsFolder, RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(resultsFolder, "report_result.md")), Is.True);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(resultsFolder, "report_result.md")), Is.True);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
||||||
Assert.That(outputFolder.Enters(resultsFolder), Is.False);
|
Assert.That(outputFolder.Enters(resultsFolder), Is.False);
|
||||||
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -69,14 +69,14 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
var resultsFolder = Path.Combine(parentFolder, "results");
|
var resultsFolder = Path.Combine(parentFolder, "results");
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, resultsFolder, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, resultsFolder, RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(resultsFolder, "report_result.md")), Is.True);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(resultsFolder, "report_result.md")), Is.True);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(parentFolder, "report.pdf")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(parentFolder, "report.pdf")), Is.False);
|
||||||
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "2026")), Is.True);
|
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "2026")), Is.True);
|
||||||
Assert.That(outputFolder.Enters(parentFolder), Is.True);
|
Assert.That(outputFolder.Enters(parentFolder), Is.True);
|
||||||
Assert.That(outputFolder.Enters(resultsFolder), Is.False);
|
Assert.That(outputFolder.Enters(resultsFolder), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -84,11 +84,11 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
{
|
{
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, Path.Combine(INPUT_FOLDER, "out"), RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, Path.Combine(INPUT_FOLDER, "out"), RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "output-notes.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "output-notes.md")), Is.False);
|
||||||
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "output")), Is.True);
|
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "output")), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -96,12 +96,12 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
{
|
{
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, $"{INPUT_FOLDER}-results", RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, $"{INPUT_FOLDER}-results", RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "log.csv")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "log.csv")), Is.False);
|
||||||
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
@@ -111,13 +111,13 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
var outputDirectory = withTrailingSeparator ? INPUT_FOLDER + Path.DirectorySeparatorChar : INPUT_FOLDER;
|
var outputDirectory = withTrailingSeparator ? INPUT_FOLDER + Path.DirectorySeparatorChar : INPUT_FOLDER;
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, outputDirectory, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, outputDirectory, RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "log.csv")), Is.True);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "log.csv")), Is.True);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, RESULTS_FILE_NAME)), Is.True);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, RESULTS_FILE_NAME)), Is.True);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.pdf")), Is.False);
|
||||||
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True, "Leaving out the output folder would leave out the input folder, too.");
|
Assert.That(outputFolder.Enters(Path.Combine(INPUT_FOLDER, "notes")), Is.True, "Leaving out the output folder would leave out the input folder, too.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -125,13 +125,13 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
{
|
{
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, INPUT_FOLDER, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, INPUT_FOLDER, RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result.md")), Is.True);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result.md")), Is.True);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result_2.docx")), Is.True, "A second result of the same name gets a counter.");
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result_2.docx")), Is.True, "A second result of the same name gets a counter.");
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "Report_Result.HTML")), Is.True);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "Report_Result.HTML")), Is.True);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.v2_result.md")), Is.True, "Only the last extension of the document makes way for the suffix.");
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.v2_result.md")), Is.True, "Only the last extension of the document makes way for the suffix.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -139,14 +139,14 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
{
|
{
|
||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, INPUT_FOLDER, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, INPUT_FOLDER, RESULTS_FILE_NAME);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report.md")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "result.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "result.md")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "my_results.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "my_results.md")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result_draft.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result_draft.md")), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result_.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(INPUT_FOLDER, "report_result_.md")), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -155,11 +155,11 @@ public sealed class BatchProcessingOutputFolderTests
|
|||||||
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, INPUT_FOLDER, RESULTS_FILE_NAME);
|
var outputFolder = new BatchProcessingOutputFolder(INPUT_FOLDER, INPUT_FOLDER, RESULTS_FILE_NAME);
|
||||||
var subfolder = Path.Combine(INPUT_FOLDER, "notes");
|
var subfolder = Path.Combine(INPUT_FOLDER, "notes");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(subfolder, "log.csv")), Is.False, "A run writes into the output folder itself, never into one of its subfolders.");
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(subfolder, "log.csv")), Is.False, "A run writes into the output folder itself, never into one of its subfolders.");
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(subfolder, RESULTS_FILE_NAME)), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(subfolder, RESULTS_FILE_NAME)), Is.False);
|
||||||
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(subfolder, "report_result.md")), Is.False);
|
Assert.That(outputFolder.IsOutputOfRun(Path.Combine(subfolder, "report_result.md")), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,11 +32,11 @@ public sealed class ERIServerFileListTests
|
|||||||
|
|
||||||
var isRead = ERIServerFileList.TryParse(ANSWER, out var files);
|
var isRead = ERIServerFileList.TryParse(ANSWER, out var files);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(isRead, Is.True);
|
Assert.That(isRead, Is.True);
|
||||||
Assert.That(files, Is.EqualTo(FILES));
|
Assert.That(files, Is.EqualTo(FILES));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -44,11 +44,11 @@ public sealed class ERIServerFileListTests
|
|||||||
{
|
{
|
||||||
var isRead = ERIServerFileList.TryParse("""{ "files": [ "src\\main.py" ] }""", out var files);
|
var isRead = ERIServerFileList.TryParse("""{ "files": [ "src\\main.py" ] }""", out var files);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(isRead, Is.True);
|
Assert.That(isRead, Is.True);
|
||||||
Assert.That(files, Is.EqualTo(PATH_WITH_BACKSLASH));
|
Assert.That(files, Is.EqualTo(PATH_WITH_BACKSLASH));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -56,11 +56,11 @@ public sealed class ERIServerFileListTests
|
|||||||
{
|
{
|
||||||
var isRead = ERIServerFileList.TryParse("""{ "files": [ "main.py", "", " " ] }""", out var files);
|
var isRead = ERIServerFileList.TryParse("""{ "files": [ "main.py", "", " " ] }""", out var files);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(isRead, Is.True);
|
Assert.That(isRead, Is.True);
|
||||||
Assert.That(files, Is.EqualTo(MAIN_PY));
|
Assert.That(files, Is.EqualTo(MAIN_PY));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("""[ "main.py" ]""", TestName = "A list without its object")]
|
[TestCase("""[ "main.py" ]""", TestName = "A list without its object")]
|
||||||
@@ -73,10 +73,10 @@ public sealed class ERIServerFileListTests
|
|||||||
{
|
{
|
||||||
var isRead = ERIServerFileList.TryParse(answer, out var files);
|
var isRead = ERIServerFileList.TryParse(answer, out var files);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(isRead, Is.False);
|
Assert.That(isRead, Is.False);
|
||||||
Assert.That(files, Is.Empty);
|
Assert.That(files, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,11 +45,11 @@ public sealed class ERIServerFileWritingTests
|
|||||||
{
|
{
|
||||||
var outcome = await ERIServerFiles.WriteAsync(this.baseDirectory, "src/my-server/main.py", ANSWER, NullLogger.Instance);
|
var outcome = await ERIServerFiles.WriteAsync(this.baseDirectory, "src/my-server/main.py", ANSWER, NullLogger.Instance);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outcome, Is.EqualTo(ERIServerFileOutcome.WRITTEN));
|
Assert.That(outcome, Is.EqualTo(ERIServerFileOutcome.WRITTEN));
|
||||||
Assert.That(File.ReadAllText(Path.Join(this.baseDirectory, "src", "my-server", "main.py")), Is.EqualTo(MAIN_PY));
|
Assert.That(File.ReadAllText(Path.Join(this.baseDirectory, "src", "my-server", "main.py")), Is.EqualTo(MAIN_PY));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -58,11 +58,11 @@ public sealed class ERIServerFileWritingTests
|
|||||||
var file = $"../outside-{Guid.NewGuid():N}.py";
|
var file = $"../outside-{Guid.NewGuid():N}.py";
|
||||||
var outcome = await ERIServerFiles.WriteAsync(this.baseDirectory, file, ANSWER, NullLogger.Instance);
|
var outcome = await ERIServerFiles.WriteAsync(this.baseDirectory, file, ANSWER, NullLogger.Instance);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outcome, Is.EqualTo(ERIServerFileOutcome.OUTSIDE_BASE_DIRECTORY));
|
Assert.That(outcome, Is.EqualTo(ERIServerFileOutcome.OUTSIDE_BASE_DIRECTORY));
|
||||||
Assert.That(File.Exists(Path.GetFullPath(Path.Join(this.baseDirectory, file))), Is.False);
|
Assert.That(File.Exists(Path.GetFullPath(Path.Join(this.baseDirectory, file))), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -70,11 +70,11 @@ public sealed class ERIServerFileWritingTests
|
|||||||
{
|
{
|
||||||
var outcome = await ERIServerFiles.WriteAsync(this.baseDirectory, "main.py", "## main.py\n\n```python\nprint(", NullLogger.Instance);
|
var outcome = await ERIServerFiles.WriteAsync(this.baseDirectory, "main.py", "## main.py\n\n```python\nprint(", NullLogger.Instance);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(outcome, Is.EqualTo(ERIServerFileOutcome.NO_COMPLETE_CODE_BLOCK));
|
Assert.That(outcome, Is.EqualTo(ERIServerFileOutcome.NO_COMPLETE_CODE_BLOCK));
|
||||||
Assert.That(File.Exists(Path.Join(this.baseDirectory, "main.py")), Is.False);
|
Assert.That(File.Exists(Path.Join(this.baseDirectory, "main.py")), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ public sealed class ERIServerPromptTests
|
|||||||
{
|
{
|
||||||
var lines = Lines(new ERIServerPrompt { AuthenticationMethods = [Auth.NONE, Auth.USERNAME_PASSWORD], AuthDescription = AUTH_DESCRIPTION });
|
var lines = Lines(new ERIServerPrompt { AuthenticationMethods = [Auth.NONE, Auth.USERNAME_PASSWORD], AuthDescription = AUTH_DESCRIPTION });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(lines, Does.Contain("The authentication process is planned as follows:"));
|
Assert.That(lines, Does.Contain("The authentication process is planned as follows:"));
|
||||||
Assert.That(lines, Does.Contain(AUTH_DESCRIPTION));
|
Assert.That(lines, Does.Contain(AUTH_DESCRIPTION));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ public sealed class SwotAnalysisContentPresentationTests
|
|||||||
var restoredContent = (ContentText)restoredThread!.Blocks.Single().Content!;
|
var restoredContent = (ContentText)restoredThread!.Blocks.Single().Content!;
|
||||||
var presentationWasRead = SwotAnalysisContentPresentation.TryRead(restoredContent.Presentation, out var restoredResult);
|
var presentationWasRead = SwotAnalysisContentPresentation.TryRead(restoredContent.Presentation, out var restoredResult);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(clone.Presentation, Is.Not.SameAs(content.Presentation));
|
Assert.That(clone.Presentation, Is.Not.SameAs(content.Presentation));
|
||||||
Assert.That(clone.Text, Is.EqualTo(content.Text));
|
Assert.That(clone.Text, Is.EqualTo(content.Text));
|
||||||
@@ -45,7 +45,7 @@ public sealed class SwotAnalysisContentPresentationTests
|
|||||||
Assert.That(restoredResult.MatrixHeading, Is.EqualTo("SWOT Matrix"));
|
Assert.That(restoredResult.MatrixHeading, Is.EqualTo("SWOT Matrix"));
|
||||||
Assert.That(restoredResult.Strengths.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
Assert.That(restoredResult.Strengths.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
||||||
Assert.That(restoredResult.PrioritizedActions.Items.Single().Action, Is.EqualTo("Prepare expansion"));
|
Assert.That(restoredResult.PrioritizedActions.Items.Single().Action, Is.EqualTo("Prepare expansion"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -59,11 +59,11 @@ public sealed class SwotAnalysisContentPresentationTests
|
|||||||
|
|
||||||
var presentationWasRead = SwotAnalysisContentPresentation.TryRead(content.Presentation, out _);
|
var presentationWasRead = SwotAnalysisContentPresentation.TryRead(content.Presentation, out _);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(presentationWasRead, Is.False);
|
Assert.That(presentationWasRead, Is.False);
|
||||||
Assert.That(content.Text, Is.EqualTo("Fallback Markdown"));
|
Assert.That(content.Text, Is.EqualTo("Fallback Markdown"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -90,12 +90,12 @@ public sealed class SwotAnalysisContentPresentationTests
|
|||||||
|
|
||||||
var presentationWasRead = SwotAnalysisContentPresentation.TryRead(content.Presentation, out var readResult);
|
var presentationWasRead = SwotAnalysisContentPresentation.TryRead(content.Presentation, out var readResult);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(presentationWasRead, Is.True);
|
Assert.That(presentationWasRead, Is.True);
|
||||||
Assert.That(readResult.Categories.All(category => category.Findings.Count == 0), Is.True);
|
Assert.That(readResult.Categories.All(category => category.Findings.Count == 0), Is.True);
|
||||||
Assert.That(readResult.PrioritizedActions.Items, Is.Empty);
|
Assert.That(readResult.PrioritizedActions.Items, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public sealed class SwotAnalysisMarkdownFormatterTests
|
|||||||
|
|
||||||
var markdown = SwotAnalysisMarkdownFormatter.Format(result);
|
var markdown = SwotAnalysisMarkdownFormatter.Format(result);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(markdown, Does.Contain("| | Positive | Negative |"));
|
Assert.That(markdown, Does.Contain("| | Positive | Negative |"));
|
||||||
Assert.That(Count(markdown, "Experienced team"), Is.GreaterThanOrEqualTo(2));
|
Assert.That(Count(markdown, "Experienced team"), Is.GreaterThanOrEqualTo(2));
|
||||||
@@ -26,7 +26,7 @@ public sealed class SwotAnalysisMarkdownFormatterTests
|
|||||||
Assert.That(markdown.IndexOf("## SWOT Matrix", StringComparison.Ordinal), Is.LessThan(markdown.IndexOf("## Strengths", StringComparison.Ordinal)));
|
Assert.That(markdown.IndexOf("## SWOT Matrix", StringComparison.Ordinal), Is.LessThan(markdown.IndexOf("## Strengths", StringComparison.Ordinal)));
|
||||||
Assert.That(markdown.IndexOf("## SWOT Matrix", StringComparison.Ordinal), Is.LessThan(markdown.IndexOf("## Prioritized Actions", StringComparison.Ordinal)));
|
Assert.That(markdown.IndexOf("## SWOT Matrix", StringComparison.Ordinal), Is.LessThan(markdown.IndexOf("## Prioritized Actions", StringComparison.Ordinal)));
|
||||||
Assert.That(markdown, Does.Contain("| **External** | **Opportunities:** Growing market | **Threats:** New competitor |"));
|
Assert.That(markdown, Does.Contain("| **External** | **Opportunities:** Growing market | **Threats:** New competitor |"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -45,13 +45,13 @@ public sealed class SwotAnalysisMarkdownFormatterTests
|
|||||||
.Select(heading => markdown.IndexOf(heading, StringComparison.Ordinal))
|
.Select(heading => markdown.IndexOf(heading, StringComparison.Ordinal))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(positions, Is.All.GreaterThanOrEqualTo(0));
|
Assert.That(positions, Is.All.GreaterThanOrEqualTo(0));
|
||||||
Assert.That(positions, Is.Ordered);
|
Assert.That(positions, Is.Ordered);
|
||||||
Assert.That(markdown, Does.Contain("The position is solid\\.\n\nThe competitor is the main risk\\."));
|
Assert.That(markdown, Does.Contain("The position is solid\\.\n\nThe competitor is the main risk\\."));
|
||||||
Assert.That(markdown, Does.EndWith("- **Moved the new competitor to the threats\\.** — It is external and negative\\."));
|
Assert.That(markdown, Does.EndWith("- **Moved the new competitor to the threats\\.** — It is external and negative\\."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
var parsed = SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(CreateModeration()), out var moderation, out var issue);
|
var parsed = SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(CreateModeration()), out var moderation, out var issue);
|
||||||
var result = SwotAnalysisModeration.Assemble(moderation);
|
var result = SwotAnalysisModeration.Assemble(moderation);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parsed, Is.True);
|
Assert.That(parsed, Is.True);
|
||||||
Assert.That(issue, Is.Empty);
|
Assert.That(issue, Is.Empty);
|
||||||
@@ -23,7 +23,7 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
Assert.That(result.OverallAssessment.Paragraphs, Is.EqualTo(new[] { "The position is solid.", "The competitor is the main risk." }));
|
Assert.That(result.OverallAssessment.Paragraphs, Is.EqualTo(new[] { "The position is solid.", "The competitor is the main risk." }));
|
||||||
Assert.That(result.ModerationNotes.Items.Single().Change, Is.EqualTo("Merged two strengths about the team."));
|
Assert.That(result.ModerationNotes.Items.Single().Change, Is.EqualTo("Merged two strengths about the team."));
|
||||||
Assert.That(result.PrioritizedActions.Items.Single().AddressedFactors, Is.EqualTo(new[] { "Experienced team", "Growing market" }));
|
Assert.That(result.PrioritizedActions.Items.Single().AddressedFactors, Is.EqualTo(new[] { "Experienced team", "Growing market" }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -34,11 +34,11 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
|
|
||||||
var parsed = SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out var parsedModeration, out _);
|
var parsed = SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out var parsedModeration, out _);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parsed, Is.True);
|
Assert.That(parsed, Is.True);
|
||||||
Assert.That(SwotAnalysisModeration.Assemble(parsedModeration).PrioritizedActions.Items.Single().AddressedFactors, Is.EqualTo(new[] { "Experienced team", "Growing market" }));
|
Assert.That(SwotAnalysisModeration.Assemble(parsedModeration).PrioritizedActions.Items.Single().AddressedFactors, Is.EqualTo(new[] { "Experienced team", "Growing market" }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -47,11 +47,11 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
var moderation = CreateModeration();
|
var moderation = CreateModeration();
|
||||||
moderation.Strengths.Findings.Single().FactorId = "W2";
|
moderation.Strengths.Findings.Single().FactorId = "W2";
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
||||||
Assert.That(issue, Is.EqualTo("Finding 1 of strengths has the factor_id \"W2\", which does not start with \"S\"."));
|
Assert.That(issue, Is.EqualTo("Finding 1 of strengths has the factor_id \"W2\", which does not start with \"S\"."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -60,11 +60,11 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
var moderation = CreateModeration();
|
var moderation = CreateModeration();
|
||||||
moderation.Strengths.Findings.Add(Finding("s1", "Loyal customers", "The survey shows long customer relationships."));
|
moderation.Strengths.Findings.Add(Finding("s1", "Loyal customers", "The survey shows long customer relationships."));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
||||||
Assert.That(issue, Does.Contain("\"s1\" is used more than once"));
|
Assert.That(issue, Does.Contain("\"s1\" is used more than once"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -73,11 +73,11 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
var moderation = CreateModeration();
|
var moderation = CreateModeration();
|
||||||
moderation.PrioritizedActions.Items.Single().AddressedFactorIds = ["S1", "X9"];
|
moderation.PrioritizedActions.Items.Single().AddressedFactorIds = ["S1", "X9"];
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
||||||
Assert.That(issue, Does.Contain("\"X9\""));
|
Assert.That(issue, Does.Contain("\"X9\""));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -86,11 +86,11 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
var moderation = CreateModeration();
|
var moderation = CreateModeration();
|
||||||
moderation.OverallAssessment.Text = " ";
|
moderation.OverallAssessment.Text = " ";
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
|
||||||
Assert.That(issue, Is.EqualTo("The field overall_assessment.text is empty."));
|
Assert.That(issue, Is.EqualTo("The field overall_assessment.text is empty."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -102,22 +102,22 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
withReason.ModerationNotes.Items.Clear();
|
withReason.ModerationNotes.Items.Clear();
|
||||||
withReason.ModerationNotes.EmptyMessage = "The four categories needed no changes.";
|
withReason.ModerationNotes.EmptyMessage = "The four categories needed no changes.";
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(withoutReason), out _, out var issue), Is.False);
|
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(withoutReason), out _, out var issue), Is.False);
|
||||||
Assert.That(issue, Does.StartWith("moderation_notes has no items"));
|
Assert.That(issue, Does.StartWith("moderation_notes has no items"));
|
||||||
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(withReason), out _, out _), Is.True);
|
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(withReason), out _, out _), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void AnAnswerWithoutJsonNamesTheProblemForTheModel()
|
public void AnAnswerWithoutJsonNamesTheProblemForTheModel()
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotAnalysisModerationResponseParser.TryParse("Here is my assessment.", out _, out var issue), Is.False);
|
Assert.That(SwotAnalysisModerationResponseParser.TryParse("Here is my assessment.", out _, out var issue), Is.False);
|
||||||
Assert.That(issue, Does.Contain("no complete JSON object"));
|
Assert.That(issue, Does.Contain("no complete JSON object"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -125,7 +125,7 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
{
|
{
|
||||||
var request = SwotAnalysisModeration.BuildUserRequest(CreateCategories(), "Support the investment decision", string.Empty, "A market report documents increasing demand.");
|
var request = SwotAnalysisModeration.BuildUserRequest(CreateCategories(), "Support the investment decision", string.Empty, "A market report documents increasing demand.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
foreach (var category in new[] { "strengths", "weaknesses", "opportunities", "threats" })
|
foreach (var category in new[] { "strengths", "weaknesses", "opportunities", "threats" })
|
||||||
Assert.That(request, Does.Contain($"\"{category}\""));
|
Assert.That(request, Does.Contain($"\"{category}\""));
|
||||||
@@ -137,7 +137,7 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*No additional analysis focus was provided\\.\\s*</analysis-focus>"));
|
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*No additional analysis focus was provided\\.\\s*</analysis-focus>"));
|
||||||
Assert.That(request, Does.Match("(?s)<shared-research>\\s*A market report documents increasing demand\\.\\s*</shared-research>"));
|
Assert.That(request, Does.Match("(?s)<shared-research>\\s*A market report documents increasing demand\\.\\s*</shared-research>"));
|
||||||
Assert.That(request, Does.Not.Contain("<analysis-subject>"), "The moderator works on the results, not on the material.");
|
Assert.That(request, Does.Not.Contain("<analysis-subject>"), "The moderator works on the results, not on the material.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -149,13 +149,13 @@ public sealed class SwotAnalysisModerationTests
|
|||||||
{
|
{
|
||||||
var prompt = SwotAnalysisModeration.BuildSystemPrompt("Answer in German.");
|
var prompt = SwotAnalysisModeration.BuildSystemPrompt("Answer in German.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
foreach (var key in new[] { "\"moderation_notes\"", "\"overall_assessment\"", "\"prioritized_actions\"", "\"addressed_factor_ids\"", "\"factor_id\"" })
|
foreach (var key in new[] { "\"moderation_notes\"", "\"overall_assessment\"", "\"prioritized_actions\"", "\"addressed_factor_ids\"", "\"factor_id\"" })
|
||||||
Assert.That(prompt, Does.Contain(key));
|
Assert.That(prompt, Does.Contain(key));
|
||||||
|
|
||||||
Assert.That(prompt, Does.EndWith("Answer in German."));
|
Assert.That(prompt, Does.EndWith("Answer in German."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<SwotAnalysisCategory, SwotCategoryAnalysisResult> CreateCategories() => new()
|
private static Dictionary<SwotAnalysisCategory, SwotCategoryAnalysisResult> CreateCategories() => new()
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ public sealed class SwotAnalysisSendToChatTests
|
|||||||
|
|
||||||
var chatThread = AssistantSWOTAnalysis.CreateChatThreadForResult(assistantThread, visiblePrompt, result);
|
var chatThread = AssistantSWOTAnalysis.CreateChatThreadForResult(assistantThread, visiblePrompt, result);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(chatThread.Blocks, Has.Count.EqualTo(2));
|
Assert.That(chatThread.Blocks, Has.Count.EqualTo(2));
|
||||||
Assert.That(chatThread.Blocks[0], Is.SameAs(visiblePrompt));
|
Assert.That(chatThread.Blocks[0], Is.SameAs(visiblePrompt));
|
||||||
@@ -27,7 +27,7 @@ public sealed class SwotAnalysisSendToChatTests
|
|||||||
Assert.That(((ContentText)chatThread.Blocks[1].Content!).Text, Is.EqualTo("## SWOT Matrix"));
|
Assert.That(((ContentText)chatThread.Blocks[1].Content!).Text, Is.EqualTo("## SWOT Matrix"));
|
||||||
Assert.That(chatThread.Blocks[1].Content, Is.Not.SameAs(result.Content));
|
Assert.That(chatThread.Blocks[1].Content, Is.Not.SameAs(result.Content));
|
||||||
Assert.That(chatThread.SystemPrompt, Is.EqualTo(SystemPrompts.DEFAULT));
|
Assert.That(chatThread.SystemPrompt, Is.EqualTo(SystemPrompts.DEFAULT));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -38,11 +38,11 @@ public sealed class SwotAnalysisSendToChatTests
|
|||||||
|
|
||||||
var chatThread = AssistantSWOTAnalysis.CreateChatThreadForResult(assistantThread, VisiblePrompt(result.Time.AddTicks(-1)), result);
|
var chatThread = AssistantSWOTAnalysis.CreateChatThreadForResult(assistantThread, VisiblePrompt(result.Time.AddTicks(-1)), result);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(assistantThread.IsWebAddressGivenToTheModel(new Uri(ADDRESS_A_MODEL_CHOSE)), Is.True, "The hidden request counts as a message of the user.");
|
Assert.That(assistantThread.IsWebAddressGivenToTheModel(new Uri(ADDRESS_A_MODEL_CHOSE)), Is.True, "The hidden request counts as a message of the user.");
|
||||||
Assert.That(chatThread.IsWebAddressGivenToTheModel(new Uri(ADDRESS_A_MODEL_CHOSE)), Is.False);
|
Assert.That(chatThread.IsWebAddressGivenToTheModel(new Uri(ADDRESS_A_MODEL_CHOSE)), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -54,11 +54,11 @@ public sealed class SwotAnalysisSendToChatTests
|
|||||||
|
|
||||||
var chatThread = AssistantSWOTAnalysis.CreateChatThreadForResult(assistantThread, VisiblePrompt(result.Time.AddTicks(-1)), result);
|
var chatThread = AssistantSWOTAnalysis.CreateChatThreadForResult(assistantThread, VisiblePrompt(result.Time.AddTicks(-1)), result);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(chatThread.ChatId, Is.EqualTo(assistantThread.ChatId));
|
Assert.That(chatThread.ChatId, Is.EqualTo(assistantThread.ChatId));
|
||||||
Assert.That(chatThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
Assert.That(chatThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ public sealed class SwotAnalysisSessionStateTests
|
|||||||
var reader = new AssistantSessionStateReader(writer.ToDictionary(), "SWOT Analysis");
|
var reader = new AssistantSessionStateReader(writer.ToDictionary(), "SWOT Analysis");
|
||||||
reader.Restore(key, value => restored = value);
|
reader.Restore(key, value => restored = value);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(restored, Is.Not.Null);
|
Assert.That(restored, Is.Not.Null);
|
||||||
Assert.That(restored!.MatrixHeading, Is.EqualTo("SWOT Matrix"));
|
Assert.That(restored!.MatrixHeading, Is.EqualTo("SWOT Matrix"));
|
||||||
Assert.That(restored.Strengths.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
Assert.That(restored.Strengths.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -44,12 +44,12 @@ public sealed class SwotAnalysisSessionStateTests
|
|||||||
var reader = new AssistantSessionStateReader(writer.ToDictionary(), "SWOT Analysis");
|
var reader = new AssistantSessionStateReader(writer.ToDictionary(), "SWOT Analysis");
|
||||||
reader.RestoreDictionary(key, restored);
|
reader.RestoreDictionary(key, restored);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(restored[SwotAnalysisCategory.STRENGTHS].Status, Is.EqualTo(SwotCategoryAnalysisStatus.COMPLETED));
|
Assert.That(restored[SwotAnalysisCategory.STRENGTHS].Status, Is.EqualTo(SwotCategoryAnalysisStatus.COMPLETED));
|
||||||
Assert.That(restored[SwotAnalysisCategory.STRENGTHS].Result!.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
Assert.That(restored[SwotAnalysisCategory.STRENGTHS].Result!.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
||||||
Assert.That(restored[SwotAnalysisCategory.WEAKNESSES].Status, Is.EqualTo(SwotCategoryAnalysisStatus.RUNNING));
|
Assert.That(restored[SwotAnalysisCategory.WEAKNESSES].Status, Is.EqualTo(SwotCategoryAnalysisStatus.RUNNING));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SwotFinding Finding() => new()
|
private static SwotFinding Finding() => new()
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
|
|
||||||
var parsed = SwotCategoryAnalysisResponseParser.TryParse(JsonSerializer.Serialize(result), out var parsedResult, out var issue);
|
var parsed = SwotCategoryAnalysisResponseParser.TryParse(JsonSerializer.Serialize(result), out var parsedResult, out var issue);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parsed, Is.True);
|
Assert.That(parsed, Is.True);
|
||||||
Assert.That(issue, Is.Empty);
|
Assert.That(issue, Is.Empty);
|
||||||
Assert.That(parsedResult.EmptyMessage, Is.Empty);
|
Assert.That(parsedResult.EmptyMessage, Is.Empty);
|
||||||
Assert.That(parsedResult.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
Assert.That(parsedResult.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -29,11 +29,11 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
{
|
{
|
||||||
var parsed = SwotCategoryAnalysisResponseParser.TryParse("I cannot analyze this category.", out _, out var issue);
|
var parsed = SwotCategoryAnalysisResponseParser.TryParse("I cannot analyze this category.", out _, out var issue);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parsed, Is.False);
|
Assert.That(parsed, Is.False);
|
||||||
Assert.That(issue, Does.Contain("no complete JSON object"));
|
Assert.That(issue, Does.Contain("no complete JSON object"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -41,11 +41,11 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
{
|
{
|
||||||
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"findings": [{"summary": "Experienced team",}""", out _, out var issue);
|
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"findings": [{"summary": "Experienced team",}""", out _, out var issue);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parsed, Is.False);
|
Assert.That(parsed, Is.False);
|
||||||
Assert.That(issue, Is.Not.Empty);
|
Assert.That(issue, Is.Not.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -53,11 +53,11 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
{
|
{
|
||||||
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"empty_message": null, "findings": []}""", out _, out var issue);
|
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"empty_message": null, "findings": []}""", out _, out var issue);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parsed, Is.False);
|
Assert.That(parsed, Is.False);
|
||||||
Assert.That(issue, Does.StartWith("The JSON object cannot be read:"));
|
Assert.That(issue, Does.StartWith("The JSON object cannot be read:"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -65,11 +65,11 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
{
|
{
|
||||||
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"empty_message": "", "findings": [null, {"summary": "Experienced team", "explanation": "Long tenure."}]}""", out var result, out _);
|
var parsed = SwotCategoryAnalysisResponseParser.TryParse("""{"empty_message": "", "findings": [null, {"summary": "Experienced team", "explanation": "Long tenure."}]}""", out var result, out _);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parsed, Is.True);
|
Assert.That(parsed, Is.True);
|
||||||
Assert.That(result.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
Assert.That(result.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -81,13 +81,13 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
});
|
});
|
||||||
var invalidEmptyResponse = JsonSerializer.Serialize(new SwotCategoryAnalysisResult());
|
var invalidEmptyResponse = JsonSerializer.Serialize(new SwotCategoryAnalysisResult());
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(validEmptyResponse, out var result, out _), Is.True);
|
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(validEmptyResponse, out var result, out _), Is.True);
|
||||||
Assert.That(result.Findings, Is.Empty);
|
Assert.That(result.Findings, Is.Empty);
|
||||||
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(invalidEmptyResponse, out _, out var issue), Is.False);
|
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(invalidEmptyResponse, out _, out var issue), Is.False);
|
||||||
Assert.That(issue, Does.Contain("empty_message"));
|
Assert.That(issue, Does.Contain("empty_message"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -101,13 +101,13 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
result.Findings[0].Explanation = string.Empty;
|
result.Findings[0].Explanation = string.Empty;
|
||||||
var responseWithoutExplanation = JsonSerializer.Serialize(result);
|
var responseWithoutExplanation = JsonSerializer.Serialize(result);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(responseWithContradictoryEmptyMessage, out _, out var contradictionIssue), Is.False);
|
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(responseWithContradictoryEmptyMessage, out _, out var contradictionIssue), Is.False);
|
||||||
Assert.That(contradictionIssue, Does.Contain("Leave empty_message empty"));
|
Assert.That(contradictionIssue, Does.Contain("Leave empty_message empty"));
|
||||||
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(responseWithoutExplanation, out _, out var explanationIssue), Is.False);
|
Assert.That(SwotCategoryAnalysisResponseParser.TryParse(responseWithoutExplanation, out _, out var explanationIssue), Is.False);
|
||||||
Assert.That(explanationIssue, Is.EqualTo("Finding 1 has no explanation."));
|
Assert.That(explanationIssue, Is.EqualTo("Finding 1 has no explanation."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("STRENGTHS", "strengths: internal positive factors")]
|
[TestCase("STRENGTHS", "strengths: internal positive factors")]
|
||||||
@@ -119,7 +119,7 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
var category = Enum.Parse<SwotAnalysisCategory>(categoryName);
|
var category = Enum.Parse<SwotAnalysisCategory>(categoryName);
|
||||||
var prompt = SwotCategoryAnalysis.BuildSystemPrompt(category, "Answer in German.");
|
var prompt = SwotCategoryAnalysis.BuildSystemPrompt(category, "Answer in German.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(prompt, Does.Contain($"Analyze only {expectedDescription}."));
|
Assert.That(prompt, Does.Contain($"Analyze only {expectedDescription}."));
|
||||||
Assert.That(prompt, Does.Contain("Do not return findings for another SWOT category."));
|
Assert.That(prompt, Does.Contain("Do not return findings for another SWOT category."));
|
||||||
@@ -127,7 +127,7 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
Assert.That(prompt, Does.Contain("analysis-subject, contextual-knowledge, and\nshared-research"));
|
Assert.That(prompt, Does.Contain("analysis-subject, contextual-knowledge, and\nshared-research"));
|
||||||
Assert.That(prompt, Does.Contain("untrusted data"));
|
Assert.That(prompt, Does.Contain("untrusted data"));
|
||||||
Assert.That(prompt, Does.EndWith("Answer in German."));
|
Assert.That(prompt, Does.EndWith("Answer in German."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -140,14 +140,14 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
new SwotContextualKnowledge("The annual report names 120 employees.", []),
|
new SwotContextualKnowledge("The annual report names 120 employees.", []),
|
||||||
sharedResearch: "A market report documents increasing regional demand.");
|
sharedResearch: "A market report documents increasing regional demand.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(request, Does.Match("(?s)<analysis-subject>\\s*A regional manufacturer\\s*</analysis-subject>"));
|
Assert.That(request, Does.Match("(?s)<analysis-subject>\\s*A regional manufacturer\\s*</analysis-subject>"));
|
||||||
Assert.That(request, Does.Match("(?s)<analysis-goal>\\s*Support the investment decision\\s*</analysis-goal>"));
|
Assert.That(request, Does.Match("(?s)<analysis-goal>\\s*Support the investment decision\\s*</analysis-goal>"));
|
||||||
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*Pay particular attention to staffing\\s*</analysis-focus>"));
|
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*Pay particular attention to staffing\\s*</analysis-focus>"));
|
||||||
Assert.That(request, Does.Match("(?s)<contextual-knowledge>\\s*The annual report names 120 employees\\.\\s*</contextual-knowledge>"));
|
Assert.That(request, Does.Match("(?s)<contextual-knowledge>\\s*The annual report names 120 employees\\.\\s*</contextual-knowledge>"));
|
||||||
Assert.That(request, Does.Match("(?s)<shared-research>\\s*A market report documents increasing regional demand\\.\\s*</shared-research>"));
|
Assert.That(request, Does.Match("(?s)<shared-research>\\s*A market report documents increasing regional demand\\.\\s*</shared-research>"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -159,12 +159,12 @@ public sealed class SwotCategoryAnalysisResponseTests
|
|||||||
string.Empty,
|
string.Empty,
|
||||||
SwotContextualKnowledge.NONE);
|
SwotContextualKnowledge.NONE);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(request, Does.Not.Contain("<shared-research>"));
|
Assert.That(request, Does.Not.Contain("<shared-research>"));
|
||||||
Assert.That(request, Does.Contain("No additional analysis focus was provided."));
|
Assert.That(request, Does.Contain("No additional analysis focus was provided."));
|
||||||
Assert.That(request, Does.Contain("No additional contextual knowledge was provided."));
|
Assert.That(request, Does.Contain("No additional contextual knowledge was provided."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SwotCategoryAnalysisResult CreateResult() => new()
|
private static SwotCategoryAnalysisResult CreateResult() => new()
|
||||||
|
|||||||
@@ -22,11 +22,11 @@ public sealed class SwotContextualKnowledgeTests
|
|||||||
var oneImage = new SwotContextualKnowledge(string.Empty, [Image("chart.png")]);
|
var oneImage = new SwotContextualKnowledge(string.Empty, [Image("chart.png")]);
|
||||||
var twoImages = new SwotContextualKnowledge(string.Empty, [Image("chart.png"), Image("photo.jpg")]);
|
var twoImages = new SwotContextualKnowledge(string.Empty, [Image("chart.png"), Image("photo.jpg")]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(oneImage.Describe(), Is.EqualTo("The image attached to this message belongs to the contextual knowledge."));
|
Assert.That(oneImage.Describe(), Is.EqualTo("The image attached to this message belongs to the contextual knowledge."));
|
||||||
Assert.That(twoImages.Describe(), Is.EqualTo("The 2 images attached to this message belong to the contextual knowledge."));
|
Assert.That(twoImages.Describe(), Is.EqualTo("The 2 images attached to this message belong to the contextual knowledge."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -34,11 +34,11 @@ public sealed class SwotContextualKnowledgeTests
|
|||||||
{
|
{
|
||||||
var description = new SwotContextualKnowledge(DOCUMENTS, [Image("chart.png")]).Describe();
|
var description = new SwotContextualKnowledge(DOCUMENTS, [Image("chart.png")]).Describe();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(description, Does.StartWith(DOCUMENTS));
|
Assert.That(description, Does.StartWith(DOCUMENTS));
|
||||||
Assert.That(description, Does.EndWith("The image attached to this message belongs to the contextual knowledge."));
|
Assert.That(description, Does.EndWith("The image attached to this message belongs to the contextual knowledge."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileAttachmentImage Image(string fileName) => new(fileName, $"/tmp/{fileName}", 1024);
|
private static FileAttachmentImage Image(string fileName) => new(fileName, $"/tmp/{fileName}", 1024);
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ public sealed class SwotQuadrantLabelsTests
|
|||||||
[TestCase(SwotAnalysisCategory.THREATS, "Risiken", "Extern · Negativ")]
|
[TestCase(SwotAnalysisCategory.THREATS, "Risiken", "Extern · Negativ")]
|
||||||
public void EveryQuadrantNamesItselfAndWhereItLies(SwotAnalysisCategory category, string expectedName, string expectedAxes)
|
public void EveryQuadrantNamesItselfAndWhereItLies(SwotAnalysisCategory category, string expectedName, string expectedAxes)
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(LABELS.NameOf(category), Is.EqualTo(expectedName));
|
Assert.That(LABELS.NameOf(category), Is.EqualTo(expectedName));
|
||||||
Assert.That(LABELS.AxesOf(category), Is.EqualTo(expectedAxes));
|
Assert.That(LABELS.AxesOf(category), Is.EqualTo(expectedAxes));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,11 +14,11 @@ public sealed class SwotRepairRoundTests
|
|||||||
{
|
{
|
||||||
var request = SwotModelAnswer.RepairRequestOf("Finding 1 has no explanation.");
|
var request = SwotModelAnswer.RepairRequestOf("Finding 1 has no explanation.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(request, Does.Contain("Finding 1 has no explanation."));
|
Assert.That(request, Does.Contain("Finding 1 has no explanation."));
|
||||||
Assert.That(request, Does.Contain("complete corrected JSON object"));
|
Assert.That(request, Does.Contain("complete corrected JSON object"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -28,12 +28,12 @@ public sealed class SwotRepairRoundTests
|
|||||||
|
|
||||||
var read = await ReadAsync(VALID_ANSWER, repairRequests, ANSWER_WITHOUT_JSON);
|
var read = await ReadAsync(VALID_ANSWER, repairRequests, ANSWER_WITHOUT_JSON);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(read.Result?.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
Assert.That(read.Result?.Findings.Single().Summary, Is.EqualTo("Experienced team"));
|
||||||
Assert.That(read.Issue, Is.Empty);
|
Assert.That(read.Issue, Is.Empty);
|
||||||
Assert.That(repairRequests, Is.Empty);
|
Assert.That(repairRequests, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -43,12 +43,12 @@ public sealed class SwotRepairRoundTests
|
|||||||
|
|
||||||
var read = await ReadAsync(ANSWER_WITHOUT_EXPLANATION, repairRequests, VALID_ANSWER);
|
var read = await ReadAsync(ANSWER_WITHOUT_EXPLANATION, repairRequests, VALID_ANSWER);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(read.Result?.Findings.Single().Explanation, Is.EqualTo("The documents describe long employee tenure."));
|
Assert.That(read.Result?.Findings.Single().Explanation, Is.EqualTo("The documents describe long employee tenure."));
|
||||||
Assert.That(read.Issue, Is.Empty);
|
Assert.That(read.Issue, Is.Empty);
|
||||||
Assert.That(repairRequests.Single(), Is.EqualTo("Finding 1 has no explanation."));
|
Assert.That(repairRequests.Single(), Is.EqualTo("Finding 1 has no explanation."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -58,12 +58,12 @@ public sealed class SwotRepairRoundTests
|
|||||||
|
|
||||||
var read = await ReadAsync(ANSWER_WITHOUT_EXPLANATION, repairRequests, ANSWER_WITHOUT_JSON);
|
var read = await ReadAsync(ANSWER_WITHOUT_EXPLANATION, repairRequests, ANSWER_WITHOUT_JSON);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(read.Result, Is.Null);
|
Assert.That(read.Result, Is.Null);
|
||||||
Assert.That(read.Issue, Does.Contain("no complete JSON object"));
|
Assert.That(read.Issue, Does.Contain("no complete JSON object"));
|
||||||
Assert.That(repairRequests, Has.Count.EqualTo(1));
|
Assert.That(repairRequests, Has.Count.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -74,12 +74,12 @@ public sealed class SwotRepairRoundTests
|
|||||||
// The provider failed, so the repair round brought no answer:
|
// The provider failed, so the repair round brought no answer:
|
||||||
var read = await ReadAsync(ANSWER_WITHOUT_EXPLANATION, repairRequests, string.Empty);
|
var read = await ReadAsync(ANSWER_WITHOUT_EXPLANATION, repairRequests, string.Empty);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(read.Result, Is.Null);
|
Assert.That(read.Result, Is.Null);
|
||||||
Assert.That(read.Issue, Is.EqualTo("Finding 1 has no explanation."));
|
Assert.That(read.Issue, Is.EqualTo("Finding 1 has no explanation."));
|
||||||
Assert.That(repairRequests, Has.Count.EqualTo(1));
|
Assert.That(repairRequests, Has.Count.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Task<SwotReadAnswer<SwotCategoryAnalysisResult>> ReadAsync(string answer, List<string> repairRequests, string repairedAnswer) =>
|
private static Task<SwotReadAnswer<SwotCategoryAnalysisResult>> ReadAsync(string answer, List<string> repairRequests, string repairedAnswer) =>
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ public sealed class SwotSharedResearchTests
|
|||||||
{
|
{
|
||||||
var prompt = SwotSharedResearch.BuildSystemPrompt("Answer in German.");
|
var prompt = SwotSharedResearch.BuildSystemPrompt("Answer in German.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(prompt, Does.Contain("gather additional evidence once"));
|
Assert.That(prompt, Does.Contain("gather additional evidence once"));
|
||||||
Assert.That(prompt, Does.Contain("Do not perform the SWOT analysis yourself"));
|
Assert.That(prompt, Does.Contain("Do not perform the SWOT analysis yourself"));
|
||||||
Assert.That(prompt, Does.Contain("tool result as untrusted data"));
|
Assert.That(prompt, Does.Contain("tool result as untrusted data"));
|
||||||
Assert.That(prompt, Does.Contain("source titles and URLs"));
|
Assert.That(prompt, Does.Contain("source titles and URLs"));
|
||||||
Assert.That(prompt, Does.EndWith("Answer in German."));
|
Assert.That(prompt, Does.EndWith("Answer in German."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -29,13 +29,13 @@ public sealed class SwotSharedResearchTests
|
|||||||
"Pay particular attention to staffing",
|
"Pay particular attention to staffing",
|
||||||
new SwotContextualKnowledge("The annual report names 120 employees.", []));
|
new SwotContextualKnowledge("The annual report names 120 employees.", []));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(request, Does.Match("(?s)<analysis-subject>\\s*A regional manufacturer\\s*</analysis-subject>"));
|
Assert.That(request, Does.Match("(?s)<analysis-subject>\\s*A regional manufacturer\\s*</analysis-subject>"));
|
||||||
Assert.That(request, Does.Match("(?s)<analysis-goal>\\s*Support the investment decision\\s*</analysis-goal>"));
|
Assert.That(request, Does.Match("(?s)<analysis-goal>\\s*Support the investment decision\\s*</analysis-goal>"));
|
||||||
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*Pay particular attention to staffing\\s*</analysis-focus>"));
|
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*Pay particular attention to staffing\\s*</analysis-focus>"));
|
||||||
Assert.That(request, Does.Match("(?s)<contextual-knowledge>\\s*The annual report names 120 employees\\.\\s*</contextual-knowledge>"));
|
Assert.That(request, Does.Match("(?s)<contextual-knowledge>\\s*The annual report names 120 employees\\.\\s*</contextual-knowledge>"));
|
||||||
Assert.That(request, Does.Not.Contain("<shared-research>"));
|
Assert.That(request, Does.Not.Contain("<shared-research>"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,11 +33,11 @@ public sealed class SendToReceiverTests
|
|||||||
{
|
{
|
||||||
var data = destination.GetData();
|
var data = destination.GetData();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(data.Event, Is.Not.EqualTo(Event.NONE), "Without an event of its own, the text has nothing to travel with.");
|
Assert.That(data.Event, Is.Not.EqualTo(Event.NONE), "Without an event of its own, the text has nothing to travel with.");
|
||||||
Assert.That(PagesOf(data.Route), Has.Count.EqualTo(1), "The text is taken by the page which opens under the route.");
|
Assert.That(PagesOf(data.Route), Has.Count.EqualTo(1), "The text is taken by the page which opens under the route.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCaseSource(nameof(Destinations))]
|
[TestCaseSource(nameof(Destinations))]
|
||||||
@@ -49,22 +49,22 @@ public sealed class SendToReceiverTests
|
|||||||
|
|
||||||
var applyMethod = page.GetMethod("ApplyTextLaunchPayload", BindingFlags.Instance | BindingFlags.NonPublic);
|
var applyMethod = page.GetMethod("ApplyTextLaunchPayload", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(IsAssistant(page), Is.True, $"{page.Name} is expected to be an assistant.");
|
Assert.That(IsAssistant(page), Is.True, $"{page.Name} is expected to be an assistant.");
|
||||||
Assert.That(applyMethod, Is.Not.Null);
|
Assert.That(applyMethod, Is.Not.Null);
|
||||||
Assert.That(applyMethod!.DeclaringType, Is.Not.EqualTo(applyMethod.GetBaseDefinition().DeclaringType), $"{page.Name} allows sending to it, so it has to put the text sent to it into a field.");
|
Assert.That(applyMethod!.DeclaringType, Is.Not.EqualTo(applyMethod.GetBaseDefinition().DeclaringType), $"{page.Name} allows sending to it, so it has to put the text sent to it into a field.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TextForTheChatGoesIntoTheFieldForTheNextMessage()
|
public void TextForTheChatGoesIntoTheFieldForTheNextMessage()
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(AssistantLaunchService.TextEventFor(AIStudio.Tools.Components.CHAT), Is.EqualTo(Event.SEND_TO_CHAT_INPUT));
|
Assert.That(AssistantLaunchService.TextEventFor(AIStudio.Tools.Components.CHAT), Is.EqualTo(Event.SEND_TO_CHAT_INPUT));
|
||||||
Assert.That(AssistantLaunchService.TextEventFor(AIStudio.Tools.Components.SLIDE_BUILDER_ASSISTANT), Is.EqualTo(Event.SEND_TO_SLIDE_BUILDER_ASSISTANT));
|
Assert.That(AssistantLaunchService.TextEventFor(AIStudio.Tools.Components.SLIDE_BUILDER_ASSISTANT), Is.EqualTo(Event.SEND_TO_SLIDE_BUILDER_ASSISTANT));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Type> PagesOf(string route) => APP.GetTypes()
|
private static List<Type> PagesOf(string route) => APP.GetTypes()
|
||||||
|
|||||||
@@ -67,11 +67,11 @@ public sealed class VisualBriefingRestrictionsTests
|
|||||||
var loaded = await this.CreateStore().LoadAsync(created.BriefingId);
|
var loaded = await this.CreateStore().LoadAsync(created.BriefingId);
|
||||||
|
|
||||||
Assert.That(loaded, Is.Not.Null);
|
Assert.That(loaded, Is.Not.Null);
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(loaded!.Settings.Instruction, Is.EqualTo("Summarize the minutes."), "The save did not happen.");
|
Assert.That(loaded!.Settings.Instruction, Is.EqualTo("Summarize the minutes."), "The save did not happen.");
|
||||||
AssertSame(loaded.Restrictions, FROM_MAILBOX);
|
AssertSame(loaded.Restrictions, FROM_MAILBOX);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -127,23 +127,26 @@ public sealed class VisualBriefingRestrictionsTests
|
|||||||
|
|
||||||
var exception = Assert.Throws<VisualBriefingBuildException>(() => VisualBriefingBuildOrchestrator.ValidateProviderMaySeeContent(restricted, cloudProvider));
|
var exception = Assert.Throws<VisualBriefingBuildException>(() => VisualBriefingBuildOrchestrator.ValidateProviderMaySeeContent(restricted, cloudProvider));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(exception!.Code, Is.EqualTo(VisualBriefingFailureCode.PROVIDER_NOT_ALLOWED));
|
Assert.That(exception!.Code, Is.EqualTo(VisualBriefingFailureCode.PROVIDER_NOT_ALLOWED));
|
||||||
Assert.DoesNotThrow(() => VisualBriefingBuildOrchestrator.ValidateProviderMaySeeContent(restricted, selfHostedProvider));
|
Assert.DoesNotThrow(() => VisualBriefingBuildOrchestrator.ValidateProviderMaySeeContent(restricted, selfHostedProvider));
|
||||||
Assert.DoesNotThrow(() => VisualBriefingBuildOrchestrator.ValidateProviderMaySeeContent(new VisualBriefingManifest(), cloudProvider), "A briefing nothing was sent to may use any provider.");
|
Assert.DoesNotThrow(() => VisualBriefingBuildOrchestrator.ValidateProviderMaySeeContent(new VisualBriefingManifest(), cloudProvider), "A briefing nothing was sent to may use any provider.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private VisualBriefingStore CreateStore() => new(null!, NullLogger<VisualBriefingStore>.Instance, new VisualBriefingStorageOptions { DataDirectory = this.dataDirectory });
|
private VisualBriefingStore CreateStore() => new(null!, NullLogger<VisualBriefingStore>.Instance, new VisualBriefingStorageOptions { DataDirectory = this.dataDirectory });
|
||||||
|
|
||||||
private static void AssertSame(ChatRestrictions actual, ChatRestrictions expected) => Assert.Multiple(() =>
|
private static void AssertSame(ChatRestrictions actual, ChatRestrictions expected)
|
||||||
{
|
{
|
||||||
Assert.That(actual.ProviderConfidence, Is.EqualTo(expected.ProviderConfidence));
|
using (Assert.EnterMultipleScope())
|
||||||
Assert.That(actual.DataSecurity, Is.EqualTo(expected.DataSecurity));
|
{
|
||||||
Assert.That(actual.OutboundData, Is.EqualTo(expected.OutboundData));
|
Assert.That(actual.ProviderConfidence, Is.EqualTo(expected.ProviderConfidence));
|
||||||
Assert.That(actual.WebAddressesWrittenByModels, Is.EquivalentTo(expected.WebAddressesWrittenByModels));
|
Assert.That(actual.DataSecurity, Is.EqualTo(expected.DataSecurity));
|
||||||
});
|
Assert.That(actual.OutboundData, Is.EqualTo(expected.OutboundData));
|
||||||
|
Assert.That(actual.WebAddressesWrittenByModels, Is.EquivalentTo(expected.WebAddressesWrittenByModels));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private AIStudio.Settings.Provider AddProvider(string id, LLMProviders llmProvider, bool isSelfHosted)
|
private AIStudio.Settings.Provider AddProvider(string id, LLMProviders llmProvider, bool isSelfHosted)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ public sealed class AttachingAFileAgainTests
|
|||||||
File.WriteAllText(path, "The board met on Monday and on Tuesday.");
|
File.WriteAllText(path, "The board met on Monday and on Tuesday.");
|
||||||
attachments.AttachInPlaceOfSameFile(FileAttachment.FromPath(path));
|
attachments.AttachInPlaceOfSameFile(FileAttachment.FromPath(path));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(attachments.Count(attachment => attachment.FilePath == path), Is.EqualTo(1), "The message would go with the old and the new text of the file.");
|
Assert.That(attachments.Count(attachment => attachment.FilePath == path), Is.EqualTo(1), "The message would go with the old and the new text of the file.");
|
||||||
Assert.That(attachments.Single(attachment => attachment.FilePath == path).ProvenanceId, Is.Null, "Attaching a file again means the file as it is now.");
|
Assert.That(attachments.Single(attachment => attachment.FilePath == path).ProvenanceId, Is.Null, "Attaching a file again means the file as it is now.");
|
||||||
Assert.That(attachments, Does.Contain(otherFile));
|
Assert.That(attachments, Does.Contain(otherFile));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -81,13 +81,13 @@ public sealed class AttachmentProvenanceTests
|
|||||||
var firstRequest = await this.PrepareAsync(message);
|
var firstRequest = await this.PrepareAsync(message);
|
||||||
var secondRequest = await this.PrepareAsync(message);
|
var secondRequest = await this.PrepareAsync(message);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Not.Null);
|
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Not.Null);
|
||||||
Assert.That(firstRequest, Does.Contain("The board met on Monday."));
|
Assert.That(firstRequest, Does.Contain("The board met on Monday."));
|
||||||
Assert.That(secondRequest, Is.EqualTo(firstRequest));
|
Assert.That(secondRequest, Is.EqualTo(firstRequest));
|
||||||
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1), "Every request read every document anew, which a long chat with large documents paid for with every message.");
|
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1), "Every request read every document anew, which a long chat with large documents paid for with every message.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -100,11 +100,11 @@ public sealed class AttachmentProvenanceTests
|
|||||||
await message.StoreAttachedDocumentsAsync(this.store.Client, Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
|
await message.StoreAttachedDocumentsAsync(this.store.Client, Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
|
||||||
var prepared = await this.PrepareAsync(message);
|
var prepared = await this.PrepareAsync(message);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(prepared, Does.Contain("The board met on Monday."), "Every request of the chat sends the same text, which is what the user can look at.");
|
Assert.That(prepared, Does.Contain("The board met on Monday."), "Every request of the chat sends the same text, which is what the user can look at.");
|
||||||
Assert.That(prepared, Does.Not.Contain("never met"));
|
Assert.That(prepared, Does.Not.Contain("never met"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -119,13 +119,13 @@ public sealed class AttachmentProvenanceTests
|
|||||||
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: true);
|
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: true);
|
||||||
var prepared = await this.PrepareAsync(message);
|
var prepared = await this.PrepareAsync(message);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(prepared, Does.Contain("and on Tuesday"), "The user chose that the AI works with the files as they are now.");
|
Assert.That(prepared, Does.Contain("and on Tuesday"), "The user chose that the AI works with the files as they are now.");
|
||||||
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Not.EqualTo(firstId), "The text sent before belongs to what the AI received then.");
|
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Not.EqualTo(firstId), "The text sent before belongs to what the AI received then.");
|
||||||
Assert.That(message.FileAttachments.Single().Version, Is.EqualTo(FileVersion.Of(this.documentPath)));
|
Assert.That(message.FileAttachments.Single().Version, Is.EqualTo(FileVersion.Of(this.documentPath)));
|
||||||
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(2));
|
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(2));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -138,12 +138,12 @@ public sealed class AttachmentProvenanceTests
|
|||||||
|
|
||||||
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: true);
|
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: true);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(message.HasDocumentsToStore(readChangedAgain: true), Is.False);
|
Assert.That(message.HasDocumentsToStore(readChangedAgain: true), Is.False);
|
||||||
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.EqualTo(firstId));
|
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.EqualTo(firstId));
|
||||||
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
|
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -155,11 +155,11 @@ public sealed class AttachmentProvenanceTests
|
|||||||
File.Delete(this.documentPath);
|
File.Delete(this.documentPath);
|
||||||
var prepared = await this.PrepareAsync(message);
|
var prepared = await this.PrepareAsync(message);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(message.HasDocumentsToStore(readChangedAgain: true), Is.False, "A file which is gone did not change: its stored text is all there is.");
|
Assert.That(message.HasDocumentsToStore(readChangedAgain: true), Is.False, "A file which is gone did not change: its stored text is all there is.");
|
||||||
Assert.That(prepared, Does.Contain("The board met on Monday."));
|
Assert.That(prepared, Does.Contain("The board met on Monday."));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -184,12 +184,12 @@ public sealed class AttachmentProvenanceTests
|
|||||||
var provenanceId = message.FileAttachments.Single().ProvenanceId!.Value;
|
var provenanceId = message.FileAttachments.Single().ProvenanceId!.Value;
|
||||||
var entries = await this.store.Client.GetEntriesAsync([provenanceId], CancellationToken.None);
|
var entries = await this.store.Client.GetEntriesAsync([provenanceId], CancellationToken.None);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(entries[provenanceId].Content, Does.Contain(FakeRuntime.REDACTION_MARKER), "The store keeps what the AI received, not what the file holds.");
|
Assert.That(entries[provenanceId].Content, Does.Contain(FakeRuntime.REDACTION_MARKER), "The store keeps what the AI received, not what the file holds.");
|
||||||
Assert.That(entries[provenanceId].Content, Does.Not.Contain(SUSPICIOUS_PASSAGE));
|
Assert.That(entries[provenanceId].Content, Does.Not.Contain(SUSPICIOUS_PASSAGE));
|
||||||
Assert.That(entries[provenanceId].Kind, Is.EqualTo(ProvenanceKind.ATTACHMENT));
|
Assert.That(entries[provenanceId].Kind, Is.EqualTo(ProvenanceKind.ATTACHMENT));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -202,13 +202,13 @@ public sealed class AttachmentProvenanceTests
|
|||||||
|
|
||||||
var prepared = await this.PrepareAsync(message);
|
var prepared = await this.PrepareAsync(message);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(prepared, Does.Contain("The board met on Monday."));
|
Assert.That(prepared, Does.Contain("The board met on Monday."));
|
||||||
Assert.That(prepared, Does.Not.Contain(SUSPICIOUS_PASSAGE), "Every way of content to the AI goes through the filter, with the rules of today.");
|
Assert.That(prepared, Does.Not.Contain(SUSPICIOUS_PASSAGE), "Every way of content to the AI goes through the filter, with the rules of today.");
|
||||||
Assert.That(prepared, Does.Contain(FakeRuntime.REDACTION_MARKER));
|
Assert.That(prepared, Does.Contain(FakeRuntime.REDACTION_MARKER));
|
||||||
Assert.That(this.runtime.ReadFiles, Is.Empty, "The stored text is filtered, not the file read again.");
|
Assert.That(this.runtime.ReadFiles, Is.Empty, "The stored text is filtered, not the file read again.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -222,13 +222,13 @@ public sealed class AttachmentProvenanceTests
|
|||||||
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: false);
|
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: false);
|
||||||
|
|
||||||
await using var context = this.store.CreateContext();
|
await using var context = this.store.CreateContext();
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(message.HasDocumentsToStore(readChangedAgain: false), Is.False);
|
Assert.That(message.HasDocumentsToStore(readChangedAgain: false), Is.False);
|
||||||
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.EqualTo(firstId));
|
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.EqualTo(firstId));
|
||||||
Assert.That(context.Entries.Count(), Is.EqualTo(1));
|
Assert.That(context.Entries.Count(), Is.EqualTo(1));
|
||||||
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
|
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -239,11 +239,11 @@ public sealed class AttachmentProvenanceTests
|
|||||||
await message.StoreAttachedDocumentsAsync(new NoProvenanceStoreClient("SQLite", "not available"), Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
|
await message.StoreAttachedDocumentsAsync(new NoProvenanceStoreClient("SQLite", "not available"), Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
|
||||||
var prepared = await this.PrepareAsync(message);
|
var prepared = await this.PrepareAsync(message);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Null);
|
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Null);
|
||||||
Assert.That(prepared, Does.Contain("The board met on Monday."), "Without the store, a chat works as it always did.");
|
Assert.That(prepared, Does.Contain("The board met on Monday."), "Without the store, a chat works as it always did.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -253,11 +253,11 @@ public sealed class AttachmentProvenanceTests
|
|||||||
|
|
||||||
var prepared = await this.PrepareAsync(message);
|
var prepared = await this.PrepareAsync(message);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(prepared, Does.Contain("The board met on Monday."), "A store which lost the entry, e.g., one restored from an older backup, must not cost the document.");
|
Assert.That(prepared, Does.Contain("The board met on Monday."), "A store which lost the entry, e.g., one restored from an older backup, must not cost the document.");
|
||||||
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
|
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -270,12 +270,12 @@ public sealed class AttachmentProvenanceTests
|
|||||||
await message.StoreAttachedDocumentsAsync(this.store.Client, Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
|
await message.StoreAttachedDocumentsAsync(this.store.Client, Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
|
||||||
var chatsWithEntries = await this.store.Client.GetChatIdsAsync(CancellationToken.None);
|
var chatsWithEntries = await this.store.Client.GetChatIdsAsync(CancellationToken.None);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(message.HasDocumentsToStore(readChangedAgain: false), Is.False, "An image goes to the AI as an image, not as a text to keep.");
|
Assert.That(message.HasDocumentsToStore(readChangedAgain: false), Is.False, "An image goes to the AI as an image, not as a text to keep.");
|
||||||
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Null);
|
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Null);
|
||||||
Assert.That(chatsWithEntries, Is.Empty);
|
Assert.That(chatsWithEntries, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -289,7 +289,7 @@ public sealed class AttachmentProvenanceTests
|
|||||||
using var document = JsonDocument.Parse(json);
|
using var document = JsonDocument.Parse(json);
|
||||||
var storedAttachments = document.RootElement.GetProperty("file_attachments");
|
var storedAttachments = document.RootElement.GetProperty("file_attachments");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(attachment.Normalize().ProvenanceId, Is.EqualTo(attachment.ProvenanceId), "Every request normalizes its attachments first.");
|
Assert.That(attachment.Normalize().ProvenanceId, Is.EqualTo(attachment.ProvenanceId), "Every request normalizes its attachments first.");
|
||||||
Assert.That(attachment.Normalize().Version, Is.EqualTo(attachment.Version), "Otherwise, a chat which reads changed files again would read every file again.");
|
Assert.That(attachment.Normalize().Version, Is.EqualTo(attachment.Version), "Otherwise, a chat which reads changed files again would read every file again.");
|
||||||
@@ -300,7 +300,7 @@ public sealed class AttachmentProvenanceTests
|
|||||||
Assert.That(transcript.WithFilePath(Path.Combine(this.directoryPath, "moved.md")).ProvenanceId, Is.EqualTo(transcript.ProvenanceId));
|
Assert.That(transcript.WithFilePath(Path.Combine(this.directoryPath, "moved.md")).ProvenanceId, Is.EqualTo(transcript.ProvenanceId));
|
||||||
Assert.That(loaded.FileAttachments[0].ProvenanceId, Is.EqualTo(attachment.ProvenanceId));
|
Assert.That(loaded.FileAttachments[0].ProvenanceId, Is.EqualTo(attachment.ProvenanceId));
|
||||||
Assert.That(storedAttachments[1].TryGetProperty("provenance_id", out _), Is.False, "An attachment never sent is stored as before, also in a chat template.");
|
Assert.That(storedAttachments[1].TryGetProperty("provenance_id", out _), Is.False, "An attachment never sent is stored as before, also in a chat template.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContentText MessageWithDocument() => new() { Text = "Summarize the minutes.", FileAttachments = [FileAttachment.FromPath(this.documentPath)] };
|
private ContentText MessageWithDocument() => new() { Text = "Summarize the minutes.", FileAttachments = [FileAttachment.FromPath(this.documentPath)] };
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var removed = WhileWriting(chat).TryRemove(chat, answer.Content!);
|
var removed = WhileWriting(chat).TryRemove(chat, answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(removed, Is.False);
|
Assert.That(removed, Is.False);
|
||||||
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content, answer.Content }), "The answer stays, since the job goes on writing into it.");
|
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content, answer.Content }), "The answer stays, since the job goes on writing into it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -41,13 +41,13 @@ public sealed class ChatBlockActionsTests
|
|||||||
var blocks = chat.Blocks.ToList();
|
var blocks = chat.Blocks.ToList();
|
||||||
var actions = WhileWriting(chat);
|
var actions = WhileWriting(chat);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(actions.TryRemove(chat, firstQuestion.Content!), Is.False);
|
Assert.That(actions.TryRemove(chat, firstQuestion.Content!), Is.False);
|
||||||
Assert.That(actions.TryRemove(chat, firstAnswer.Content!), Is.False);
|
Assert.That(actions.TryRemove(chat, firstAnswer.Content!), Is.False);
|
||||||
Assert.That(actions.TryRemoveForRegenerate(chat, firstAnswer.Content!), Is.False);
|
Assert.That(actions.TryRemoveForRegenerate(chat, firstAnswer.Content!), Is.False);
|
||||||
Assert.That(chat.Blocks, Is.EqualTo(blocks), "The job stores the conversation meanwhile, so no message may change, not even an older one.");
|
Assert.That(chat.Blocks, Is.EqualTo(blocks), "The job stores the conversation meanwhile, so no message may change, not even an older one.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -58,13 +58,13 @@ public sealed class ChatBlockActionsTests
|
|||||||
var chat = Chat(question, answer);
|
var chat = Chat(question, answer);
|
||||||
var actions = WhileWriting(chat);
|
var actions = WhileWriting(chat);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(actions.TryEditLastUserBlock(chat, question.Content!), Is.False);
|
Assert.That(actions.TryEditLastUserBlock(chat, question.Content!), Is.False);
|
||||||
Assert.That(actions.TryEditLastBlock(chat, question.Content!), Is.False);
|
Assert.That(actions.TryEditLastBlock(chat, question.Content!), Is.False);
|
||||||
Assert.That(chat.Blocks, Has.Count.EqualTo(2), "Both the question and the answer being written stay.");
|
Assert.That(chat.Blocks, Has.Count.EqualTo(2), "Both the question and the answer being written stay.");
|
||||||
Assert.That(chat.Draft.IsEmpty, Is.True, "The question does not become the draft, since it could not be sent anyway.");
|
Assert.That(chat.Draft.IsEmpty, Is.True, "The question does not become the draft, since it could not be sent anyway.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -75,11 +75,11 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var removed = WhileWriting(chat).TryRemoveForRegenerate(chat, answer.Content!);
|
var removed = WhileWriting(chat).TryRemoveForRegenerate(chat, answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(removed, Is.False, "Regenerating would leave a second answer next to the one being written.");
|
Assert.That(removed, Is.False, "Regenerating would leave a second answer next to the one being written.");
|
||||||
Assert.That(chat.Blocks, Has.Count.EqualTo(2));
|
Assert.That(chat.Blocks, Has.Count.EqualTo(2));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -90,11 +90,11 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var rolledBack = WhileWriting(chat).TryRollBack(chat, firstAnswer.Content!);
|
var rolledBack = WhileWriting(chat).TryRollBack(chat, firstAnswer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(rolledBack, Is.False);
|
Assert.That(rolledBack, Is.False);
|
||||||
Assert.That(chat.Blocks, Has.Count.EqualTo(4));
|
Assert.That(chat.Blocks, Has.Count.EqualTo(4));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -106,11 +106,11 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var removed = Done().TryRemove(chat, answer.Content!);
|
var removed = Done().TryRemove(chat, answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(removed, Is.True);
|
Assert.That(removed, Is.True);
|
||||||
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content }));
|
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -122,11 +122,11 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var removed = Done().TryRemoveForRegenerate(chat, answer.Content!);
|
var removed = Done().TryRemoveForRegenerate(chat, answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(removed, Is.True);
|
Assert.That(removed, Is.True);
|
||||||
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content }), "The question stays, to be answered anew.");
|
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content }), "The question stays, to be answered anew.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -138,11 +138,11 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var rolledBack = Done().TryRollBack(chat, firstAnswer.Content!);
|
var rolledBack = Done().TryRollBack(chat, firstAnswer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(rolledBack, Is.True);
|
Assert.That(rolledBack, Is.True);
|
||||||
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { firstQuestion.Content, firstAnswer.Content }));
|
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { firstQuestion.Content, firstAnswer.Content }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -153,12 +153,12 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var edited = Done().TryEditLastUserBlock(chat, question.Content!);
|
var edited = Done().TryEditLastUserBlock(chat, question.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(edited, Is.True);
|
Assert.That(edited, Is.True);
|
||||||
Assert.That(chat.Blocks, Is.Empty, "The question goes back into the composer, and the answer to it goes.");
|
Assert.That(chat.Blocks, Is.Empty, "The question goes back into the composer, and the answer to it goes.");
|
||||||
Assert.That(chat.Draft.Text, Is.EqualTo("Question"));
|
Assert.That(chat.Draft.Text, Is.EqualTo("Question"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -169,12 +169,12 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var edited = Done().TryEditLastBlock(chat, question.Content!);
|
var edited = Done().TryEditLastBlock(chat, question.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(edited, Is.True);
|
Assert.That(edited, Is.True);
|
||||||
Assert.That(chat.Blocks, Is.Empty);
|
Assert.That(chat.Blocks, Is.Empty);
|
||||||
Assert.That(chat.Draft.Text, Is.EqualTo("Question"));
|
Assert.That(chat.Draft.Text, Is.EqualTo("Question"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -185,12 +185,12 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var edited = Done().TryEditLastUserBlock(chat, new ContentText { Text = "Question of another chat" });
|
var edited = Done().TryEditLastUserBlock(chat, new ContentText { Text = "Question of another chat" });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(edited, Is.False);
|
Assert.That(edited, Is.False);
|
||||||
Assert.That(chat.Blocks, Has.Count.EqualTo(2), "Removing the last answer only makes sense together with the question to it.");
|
Assert.That(chat.Blocks, Has.Count.EqualTo(2), "Removing the last answer only makes sense together with the question to it.");
|
||||||
Assert.That(chat.Draft.IsEmpty, Is.True);
|
Assert.That(chat.Draft.IsEmpty, Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -208,11 +208,11 @@ public sealed class ChatBlockActionsTests
|
|||||||
|
|
||||||
var removed = actions.TryRemove(chat, answer.Content!);
|
var removed = actions.TryRemove(chat, answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(removed, Is.True);
|
Assert.That(removed, Is.True);
|
||||||
Assert.That(askedChatIds, Is.EqualTo(new[] { chat.ChatId }), "Only the chat being changed decides.");
|
Assert.That(askedChatIds, Is.EqualTo(new[] { chat.ChatId }), "Only the chat being changed decides.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -221,7 +221,7 @@ public sealed class ChatBlockActionsTests
|
|||||||
var actions = Done();
|
var actions = Done();
|
||||||
var content = new ContentText { Text = "Question" };
|
var content = new ContentText { Text = "Question" };
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(actions.AreAllowed(null), Is.False);
|
Assert.That(actions.AreAllowed(null), Is.False);
|
||||||
Assert.That(actions.TryRemove(null, content), Is.False);
|
Assert.That(actions.TryRemove(null, content), Is.False);
|
||||||
@@ -229,7 +229,7 @@ public sealed class ChatBlockActionsTests
|
|||||||
Assert.That(actions.TryRollBack(null, content), Is.False);
|
Assert.That(actions.TryRollBack(null, content), Is.False);
|
||||||
Assert.That(actions.TryEditLastUserBlock(null, content), Is.False);
|
Assert.That(actions.TryEditLastUserBlock(null, content), Is.False);
|
||||||
Assert.That(actions.TryEditLastBlock(null, content), Is.False);
|
Assert.That(actions.TryEditLastBlock(null, content), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -62,11 +62,11 @@ public sealed class ChatDraftAutosaveTests
|
|||||||
storage.IsBusy = false;
|
storage.IsBusy = false;
|
||||||
await storage.WaitForStoreAsync();
|
await storage.WaitForStoreAsync();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(storedWhileBusy, Is.Zero, "The AI job changes the conversation meanwhile, so storing it from elsewhere could read it in the middle of a change.");
|
Assert.That(storedWhileBusy, Is.Zero, "The AI job changes the conversation meanwhile, so storing it from elsewhere could read it in the middle of a change.");
|
||||||
Assert.That(storage.StoredChats, Is.EqualTo(new[] { chat }), "Once the answer is done, the change must still reach the disk.");
|
Assert.That(storage.StoredChats, Is.EqualTo(new[] { chat }), "Once the answer is done, the change must still reach the disk.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -46,12 +46,12 @@ public sealed class ChatDraftTests
|
|||||||
var added = draft.AddAttachments([TRANSCRIPT]);
|
var added = draft.AddAttachments([TRANSCRIPT]);
|
||||||
var addedAgain = draft.AddAttachments([TRANSCRIPT with { FileSizeBytes = 1024 }]);
|
var addedAgain = draft.AddAttachments([TRANSCRIPT with { FileSizeBytes = 1024 }]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(added, Is.True);
|
Assert.That(added, Is.True);
|
||||||
Assert.That(addedAgain, Is.False, "The same file counts once, even in a newer state.");
|
Assert.That(addedAgain, Is.False, "The same file counts once, even in a newer state.");
|
||||||
Assert.That(draft.FileAttachments, Is.EqualTo(new FileAttachment[] { TRANSCRIPT }), "The composer would show the transcript twice otherwise.");
|
Assert.That(draft.FileAttachments, Is.EqualTo(new FileAttachment[] { TRANSCRIPT }), "The composer would show the transcript twice otherwise.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -74,11 +74,11 @@ public sealed class ChatDraftTests
|
|||||||
|
|
||||||
draft.ReplaceAttachments(attachment => attachment with { FilePath = $"/moved{attachment.FilePath}" });
|
draft.ReplaceAttachments(attachment => attachment with { FilePath = $"/moved{attachment.FilePath}" });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(draft.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/moved/documents/agenda.pdf", "/moved/data/tempChats/meeting-transcript-0001.md" }));
|
Assert.That(draft.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/moved/documents/agenda.pdf", "/moved/data/tempChats/meeting-transcript-0001.md" }));
|
||||||
Assert.That(draft.MediaTranscripts, Has.Count.EqualTo(1), "A transcript stays a transcript, so removing it from the composer still deletes its file.");
|
Assert.That(draft.MediaTranscripts, Has.Count.EqualTo(1), "A transcript stays a transcript, so removing it from the composer still deletes its file.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -90,11 +90,11 @@ public sealed class ChatDraftTests
|
|||||||
|
|
||||||
draft.RestoreFromMessage(new ContentText { Text = "Summarize the minutes", FileAttachments = [MINUTES] });
|
draft.RestoreFromMessage(new ContentText { Text = "Summarize the minutes", FileAttachments = [MINUTES] });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(draft.Text, Is.EqualTo("Summarize the minutes"), "The message to edit replaces the older draft.");
|
Assert.That(draft.Text, Is.EqualTo("Summarize the minutes"), "The message to edit replaces the older draft.");
|
||||||
Assert.That(draft.FileAttachments, Is.EqualTo(new[] { MINUTES, TRANSCRIPT }), "The draft is the only owner of its transcripts, so dropping them would leave their files behind.");
|
Assert.That(draft.FileAttachments, Is.EqualTo(new[] { MINUTES, TRANSCRIPT }), "The draft is the only owner of its transcripts, so dropping them would leave their files behind.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -107,11 +107,11 @@ public sealed class ChatDraftTests
|
|||||||
|
|
||||||
draft.Clear();
|
draft.Clear();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(draft.IsEmpty, Is.True, "The attachments belong to the sent message now, and a transcript has only one owner.");
|
Assert.That(draft.IsEmpty, Is.True, "The attachments belong to the sent message now, and a transcript has only one owner.");
|
||||||
Assert.That(draft.PreviewedTexts, Is.Empty, "A text the preview read went with the message, or is of no use to the next one.");
|
Assert.That(draft.PreviewedTexts, Is.Empty, "A text the preview read went with the message, or is of no use to the next one.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -126,13 +126,13 @@ public sealed class ChatDraftTests
|
|||||||
draft.NotePreviewedText(otherFile);
|
draft.NotePreviewedText(otherFile);
|
||||||
var replacedBySecond = draft.NotePreviewedText(secondPreview);
|
var replacedBySecond = draft.NotePreviewedText(secondPreview);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(replacedByFirst, Is.Null);
|
Assert.That(replacedByFirst, Is.Null);
|
||||||
Assert.That(replacedBySecond, Is.EqualTo(firstPreview.ProvenanceId), "Nothing refers to the text of the first preview any longer, so the caller removes it from the store.");
|
Assert.That(replacedBySecond, Is.EqualTo(firstPreview.ProvenanceId), "Nothing refers to the text of the first preview any longer, so the caller removes it from the store.");
|
||||||
Assert.That(draft.PreviewedTexts, Is.EquivalentTo(new[] { otherFile, secondPreview }));
|
Assert.That(draft.PreviewedTexts, Is.EquivalentTo(new[] { otherFile, secondPreview }));
|
||||||
Assert.That(draft.GetPreviewedText(AGENDA.FilePath), Is.EqualTo(secondPreview));
|
Assert.That(draft.GetPreviewedText(AGENDA.FilePath), Is.EqualTo(secondPreview));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -148,12 +148,12 @@ public sealed class ChatDraftTests
|
|||||||
draft.MirrorAttachments([MINUTES]);
|
draft.MirrorAttachments([MINUTES]);
|
||||||
var forgotten = draft.ForgetPreviewsOfDetachedFiles();
|
var forgotten = draft.ForgetPreviewsOfDetachedFiles();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(forgotten, Is.EqualTo(new[] { agendaPreview.ProvenanceId }), "Nothing refers to the text any longer, so the caller removes it from the store.");
|
Assert.That(forgotten, Is.EqualTo(new[] { agendaPreview.ProvenanceId }), "Nothing refers to the text any longer, so the caller removes it from the store.");
|
||||||
Assert.That(draft.PreviewedTexts, Is.EqualTo(new[] { minutesPreview }));
|
Assert.That(draft.PreviewedTexts, Is.EqualTo(new[] { minutesPreview }));
|
||||||
Assert.That(draft.ForgetPreviewsOfDetachedFiles(), Is.Empty);
|
Assert.That(draft.ForgetPreviewsOfDetachedFiles(), Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -178,12 +178,12 @@ public sealed class ChatDraftTests
|
|||||||
|
|
||||||
var loaded = JsonSerializer.Deserialize<ChatThread>(JsonSerializer.Serialize(thread, WorkspaceBehaviour.JSON_OPTIONS), WorkspaceBehaviour.JSON_OPTIONS)!;
|
var loaded = JsonSerializer.Deserialize<ChatThread>(JsonSerializer.Serialize(thread, WorkspaceBehaviour.JSON_OPTIONS), WorkspaceBehaviour.JSON_OPTIONS)!;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(loaded.Draft.Text, Is.EqualTo("Summarize the meeting"));
|
Assert.That(loaded.Draft.Text, Is.EqualTo("Summarize the meeting"));
|
||||||
Assert.That(loaded.Draft.FileAttachments, Is.EqualTo(new[] { AGENDA, CHART, STAGED_TRANSCRIPT }), "A transcript loaded as a plain document would no longer be managed, and its file would never be deleted.");
|
Assert.That(loaded.Draft.FileAttachments, Is.EqualTo(new[] { AGENDA, CHART, STAGED_TRANSCRIPT }), "A transcript loaded as a plain document would no longer be managed, and its file would never be deleted.");
|
||||||
Assert.That(loaded.Draft.MediaTranscripts.Single().IsStaged, Is.True, "A staged transcript has to move into the chat directory once the chat is stored.");
|
Assert.That(loaded.Draft.MediaTranscripts.Single().IsStaged, Is.True, "A staged transcript has to move into the chat directory once the chat is stored.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -216,12 +216,12 @@ public sealed class ChatDraftTests
|
|||||||
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITH_PENDING_TRANSCRIPTS, WorkspaceBehaviour.JSON_OPTIONS)!;
|
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITH_PENDING_TRANSCRIPTS, WorkspaceBehaviour.JSON_OPTIONS)!;
|
||||||
var stored = JsonSerializer.Serialize(loaded, WorkspaceBehaviour.JSON_OPTIONS);
|
var stored = JsonSerializer.Serialize(loaded, WorkspaceBehaviour.JSON_OPTIONS);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(loaded.Draft.FileAttachments, Is.EqualTo(new FileAttachment[] { TRANSCRIPT }), "Opening the chat has to bring the transcript back into the composer.");
|
Assert.That(loaded.Draft.FileAttachments, Is.EqualTo(new FileAttachment[] { TRANSCRIPT }), "Opening the chat has to bring the transcript back into the composer.");
|
||||||
Assert.That(stored, Does.Not.Contain("pending_media_transcripts"), "The draft holds the transcript now, so storing the chat must not write the former list again.");
|
Assert.That(stored, Does.Not.Contain("pending_media_transcripts"), "The draft holds the transcript now, so storing the chat must not write the former list again.");
|
||||||
Assert.That(stored, Does.Contain("\"draft\""));
|
Assert.That(stored, Does.Contain("\"draft\""));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -56,14 +56,14 @@ public sealed class ChatRestrictionsTests
|
|||||||
|
|
||||||
foreach (var combined in new[] { fromDataSource.StricterOf(fromMailbox), fromMailbox.StricterOf(fromDataSource) })
|
foreach (var combined in new[] { fromDataSource.StricterOf(fromMailbox), fromMailbox.StricterOf(fromDataSource) })
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(combined.ProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
Assert.That(combined.ProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
||||||
Assert.That(combined.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED), "Data for self-hosted providers only keeps them as the only choice, whichever side brings it.");
|
Assert.That(combined.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED), "Data for self-hosted providers only keeps them as the only choice, whichever side brings it.");
|
||||||
Assert.That(combined.OutboundData.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
Assert.That(combined.OutboundData.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
||||||
Assert.That(combined.OutboundData.Reference, Is.EqualTo(MAILBOX_ID), "The source which demands the strictest restriction is the one to name.");
|
Assert.That(combined.OutboundData.Reference, Is.EqualTo(MAILBOX_ID), "The source which demands the strictest restriction is the one to name.");
|
||||||
Assert.That(combined.WebAddressesWrittenByModels, Is.EquivalentTo(new[] { RequestKey("https://example.org/report"), RequestKey("https://example.org/minutes") }), "What models wrote on either side never counts as given.");
|
Assert.That(combined.WebAddressesWrittenByModels, Is.EquivalentTo(new[] { RequestKey("https://example.org/report"), RequestKey("https://example.org/minutes") }), "What models wrote on either side never counts as given.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,11 +72,11 @@ public sealed class ChatRestrictionsTests
|
|||||||
{
|
{
|
||||||
var restrictions = ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("The report is on https://EXAMPLE.org/report#summary, and the minutes are on http://10.20.30.40:8000/minutes/. Write to ftp://example.org/upload.");
|
var restrictions = ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("The report is on https://EXAMPLE.org/report#summary, and the minutes are on http://10.20.30.40:8000/minutes/. Write to ftp://example.org/upload.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(restrictions.WebAddressesWrittenByModels, Is.EquivalentTo(new[] { RequestKey("https://example.org/report"), RequestKey("http://10.20.30.40:8000/minutes/") }), "Only addresses a web page could be read from count, compared as the server gets asked for them.");
|
Assert.That(restrictions.WebAddressesWrittenByModels, Is.EquivalentTo(new[] { RequestKey("https://example.org/report"), RequestKey("http://10.20.30.40:8000/minutes/") }), "Only addresses a web page could be read from count, compared as the server gets asked for them.");
|
||||||
Assert.That(ChatRestrictions.NONE.WebAddressesWrittenByModels, Is.Empty, "Adding addresses leaves NONE as it is.");
|
Assert.That(ChatRestrictions.NONE.WebAddressesWrittenByModels, Is.Empty, "Adding addresses leaves NONE as it is.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -84,11 +84,11 @@ public sealed class ChatRestrictionsTests
|
|||||||
{
|
{
|
||||||
var restrictions = new ChatRestrictions(ConfidenceLevel.MEDIUM, DataSourceSecurity.ALLOW_ANY, new(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT, OutboundDataOrigin.DATA_SOURCE, DATA_SOURCE_ID), []).WithWebAddressesWrittenByModelIn("See https://example.org/report.");
|
var restrictions = new ChatRestrictions(ConfidenceLevel.MEDIUM, DataSourceSecurity.ALLOW_ANY, new(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT, OutboundDataOrigin.DATA_SOURCE, DATA_SOURCE_ID), []).WithWebAddressesWrittenByModelIn("See https://example.org/report.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
AssertSame(restrictions.StricterOf(ChatRestrictions.NONE), restrictions);
|
AssertSame(restrictions.StricterOf(ChatRestrictions.NONE), restrictions);
|
||||||
AssertSame(ChatRestrictions.NONE.StricterOf(restrictions), restrictions);
|
AssertSame(ChatRestrictions.NONE.StricterOf(restrictions), restrictions);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -131,11 +131,11 @@ public sealed class ChatRestrictionsTests
|
|||||||
|
|
||||||
chatToStart.RequireRestrictions(new(ConfidenceLevel.HIGH, DataSourceSecurity.SELF_HOSTED, new(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES, OutboundDataOrigin.MAILBOX, MAILBOX_ID), []));
|
chatToStart.RequireRestrictions(new(ConfidenceLevel.HIGH, DataSourceSecurity.SELF_HOSTED, new(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES, OutboundDataOrigin.MAILBOX, MAILBOX_ID), []));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
AssertSame(chatToStart.CaptureRestrictions(), new(ConfidenceLevel.HIGH, DataSourceSecurity.SELF_HOSTED, new(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES, OutboundDataOrigin.MAILBOX, MAILBOX_ID), []));
|
AssertSame(chatToStart.CaptureRestrictions(), new(ConfidenceLevel.HIGH, DataSourceSecurity.SELF_HOSTED, new(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES, OutboundDataOrigin.MAILBOX, MAILBOX_ID), []));
|
||||||
AssertSame(assistantThread.CaptureRestrictions(), ChatRestrictions.NONE);
|
AssertSame(assistantThread.CaptureRestrictions(), ChatRestrictions.NONE);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -148,11 +148,11 @@ public sealed class ChatRestrictionsTests
|
|||||||
chat.RequireProviderConfidence(ConfidenceLevel.HIGH);
|
chat.RequireProviderConfidence(ConfidenceLevel.HIGH);
|
||||||
chat.RequireDataSecurity(DataSourceSecurity.SELF_HOSTED);
|
chat.RequireDataSecurity(DataSourceSecurity.SELF_HOSTED);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(snapshot.ProviderConfidence, Is.EqualTo(ConfidenceLevel.LOW), "Content which left the chat does not hold what the chat read afterward.");
|
Assert.That(snapshot.ProviderConfidence, Is.EqualTo(ConfidenceLevel.LOW), "Content which left the chat does not hold what the chat read afterward.");
|
||||||
Assert.That(snapshot.DataSecurity, Is.EqualTo(DataSourceSecurity.NOT_SPECIFIED));
|
Assert.That(snapshot.DataSecurity, Is.EqualTo(DataSourceSecurity.NOT_SPECIFIED));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -162,12 +162,12 @@ public sealed class ChatRestrictionsTests
|
|||||||
var selfHostedProvider = this.AddProvider(SELF_HOSTED_PROVIDER_ID, LLMProviders.SELF_HOSTED, isSelfHosted: true);
|
var selfHostedProvider = this.AddProvider(SELF_HOSTED_PROVIDER_ID, LLMProviders.SELF_HOSTED, isSelfHosted: true);
|
||||||
var restrictions = ChatRestrictions.NONE with { DataSecurity = DataSourceSecurity.SELF_HOSTED };
|
var restrictions = ChatRestrictions.NONE with { DataSecurity = DataSourceSecurity.SELF_HOSTED };
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(restrictions.IsLLMProviderAllowed(cloudProvider), Is.False);
|
Assert.That(restrictions.IsLLMProviderAllowed(cloudProvider), Is.False);
|
||||||
Assert.That(restrictions.IsLLMProviderAllowed(selfHostedProvider), Is.True);
|
Assert.That(restrictions.IsLLMProviderAllowed(selfHostedProvider), Is.True);
|
||||||
Assert.That(ChatRestrictions.NONE.IsLLMProviderAllowed(cloudProvider), Is.True, "Content which demands nothing may go to any provider.");
|
Assert.That(ChatRestrictions.NONE.IsLLMProviderAllowed(cloudProvider), Is.True, "Content which demands nothing may go to any provider.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -178,11 +178,11 @@ public sealed class ChatRestrictionsTests
|
|||||||
var higherLevels = Enum.GetValues<ConfidenceLevel>().Where(candidate => candidate > level).ToList();
|
var higherLevels = Enum.GetValues<ConfidenceLevel>().Where(candidate => candidate > level).ToList();
|
||||||
Assume.That(higherLevels, Is.Not.Empty, "The provider has the highest level there is, so no content can demand more of it.");
|
Assume.That(higherLevels, Is.Not.Empty, "The provider has the highest level there is, so no content can demand more of it.");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That((ChatRestrictions.NONE with { ProviderConfidence = level }).IsLLMProviderAllowed(provider), Is.True);
|
Assert.That((ChatRestrictions.NONE with { ProviderConfidence = level }).IsLLMProviderAllowed(provider), Is.True);
|
||||||
Assert.That((ChatRestrictions.NONE with { ProviderConfidence = higherLevels.Min() }).IsLLMProviderAllowed(provider), Is.False);
|
Assert.That((ChatRestrictions.NONE with { ProviderConfidence = higherLevels.Min() }).IsLLMProviderAllowed(provider), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -194,21 +194,24 @@ public sealed class ChatRestrictionsTests
|
|||||||
chat.RequireDataSecurity(DataSourceSecurity.SELF_HOSTED);
|
chat.RequireDataSecurity(DataSourceSecurity.SELF_HOSTED);
|
||||||
ChatThread? newChat = null;
|
ChatThread? newChat = null;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(chat.IsLLMProviderAllowed(cloudProvider), Is.EqualTo(chat.CaptureRestrictions().IsLLMProviderAllowed(cloudProvider)));
|
Assert.That(chat.IsLLMProviderAllowed(cloudProvider), Is.EqualTo(chat.CaptureRestrictions().IsLLMProviderAllowed(cloudProvider)));
|
||||||
Assert.That(chat.IsLLMProviderAllowed(selfHostedProvider), Is.EqualTo(chat.CaptureRestrictions().IsLLMProviderAllowed(selfHostedProvider)));
|
Assert.That(chat.IsLLMProviderAllowed(selfHostedProvider), Is.EqualTo(chat.CaptureRestrictions().IsLLMProviderAllowed(selfHostedProvider)));
|
||||||
Assert.That(newChat.IsLLMProviderAllowed(cloudProvider), Is.True, "A new chat demands nothing yet.");
|
Assert.That(newChat.IsLLMProviderAllowed(cloudProvider), Is.True, "A new chat demands nothing yet.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AssertSame(ChatRestrictions actual, ChatRestrictions expected) => Assert.Multiple(() =>
|
private static void AssertSame(ChatRestrictions actual, ChatRestrictions expected)
|
||||||
{
|
{
|
||||||
Assert.That(actual.ProviderConfidence, Is.EqualTo(expected.ProviderConfidence));
|
using (Assert.EnterMultipleScope())
|
||||||
Assert.That(actual.DataSecurity, Is.EqualTo(expected.DataSecurity));
|
{
|
||||||
Assert.That(actual.OutboundData, Is.EqualTo(expected.OutboundData));
|
Assert.That(actual.ProviderConfidence, Is.EqualTo(expected.ProviderConfidence));
|
||||||
Assert.That(actual.WebAddressesWrittenByModels, Is.EquivalentTo(expected.WebAddressesWrittenByModels));
|
Assert.That(actual.DataSecurity, Is.EqualTo(expected.DataSecurity));
|
||||||
});
|
Assert.That(actual.OutboundData, Is.EqualTo(expected.OutboundData));
|
||||||
|
Assert.That(actual.WebAddressesWrittenByModels, Is.EquivalentTo(expected.WebAddressesWrittenByModels));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string RequestKey(string address) => WebAddresses.CreateRequestKey(new Uri(address));
|
private static string RequestKey(string address) => WebAddresses.CreateRequestKey(new Uri(address));
|
||||||
|
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ public sealed class ChatThreadAugmentedDataTests
|
|||||||
|
|
||||||
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITH_AUGMENTED_DATA, WorkspaceBehaviour.JSON_OPTIONS);
|
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITH_AUGMENTED_DATA, WorkspaceBehaviour.JSON_OPTIONS);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(loaded, Is.Not.Null, "An older chat file must still load.");
|
Assert.That(loaded, Is.Not.Null, "An older chat file must still load.");
|
||||||
Assert.That(loaded!.AugmentedData, Is.Empty, "It belonged to a request long over; the next message retrieves anew.");
|
Assert.That(loaded!.AugmentedData, Is.Empty, "It belonged to a request long over; the next message retrieves anew.");
|
||||||
Assert.That(loaded.Name, Is.EqualTo("An older chat"));
|
Assert.That(loaded.Name, Is.EqualTo("An older chat"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,14 +67,14 @@ public sealed class ChatThreadEditTests
|
|||||||
thread.Remove(answer.Content!);
|
thread.Remove(answer.Content!);
|
||||||
composer.RestoreFromDraft(thread.Draft);
|
composer.RestoreFromDraft(thread.Draft);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.Blocks, Is.Empty, "Both the question and the stopped answer leave the chat.");
|
Assert.That(thread.Blocks, Is.Empty, "Both the question and the stopped answer leave the chat.");
|
||||||
Assert.That(File.Exists(transcript.FilePath), Is.True, "The composer still carries the transcript, so its file has to stay.");
|
Assert.That(File.Exists(transcript.FilePath), Is.True, "The composer still carries the transcript, so its file has to stay.");
|
||||||
Assert.That(composer.FileAttachments.Single().Exists, Is.True, "The edited message goes out with the transcript, not with a reference to a deleted file.");
|
Assert.That(composer.FileAttachments.Single().Exists, Is.True, "The edited message goes out with the transcript, not with a reference to a deleted file.");
|
||||||
Assert.That(thread.Draft.MediaTranscripts.Select(x => x.FilePath), Is.EqualTo(new[] { transcript.FilePath }), "The draft owns the transcript now: opening the chat again brings it back, and removing it from the composer deletes the file.");
|
Assert.That(thread.Draft.MediaTranscripts.Select(x => x.FilePath), Is.EqualTo(new[] { transcript.FilePath }), "The draft owns the transcript now: opening the chat again brings it back, and removing it from the composer deletes the file.");
|
||||||
Assert.That(thread.Draft.Text, Is.EqualTo("Summarize the meeting"), "The message being edited is the draft now, its text included.");
|
Assert.That(thread.Draft.Text, Is.EqualTo("Summarize the meeting"), "The message being edited is the draft now, its text included.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -101,11 +101,11 @@ public sealed class ChatThreadEditTests
|
|||||||
|
|
||||||
thread.Remove(question.Content!);
|
thread.Remove(question.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(File.Exists(transcript.FilePath), Is.False, "Nothing refers to the transcript of a deleted message any longer, so its file has to go.");
|
Assert.That(File.Exists(transcript.FilePath), Is.False, "Nothing refers to the transcript of a deleted message any longer, so its file has to go.");
|
||||||
Assert.That(thread.Draft.IsEmpty, Is.True, "A deleted message hands nothing over to the draft.");
|
Assert.That(thread.Draft.IsEmpty, Is.True, "A deleted message hands nothing over to the draft.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -121,12 +121,12 @@ public sealed class ChatThreadEditTests
|
|||||||
|
|
||||||
thread.RemoveForEditing(new ContentText { Text = "Question of another chat", FileAttachments = [transcript] });
|
thread.RemoveForEditing(new ContentText { Text = "Question of another chat", FileAttachments = [transcript] });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.Blocks, Has.Count.EqualTo(2), "The message to edit is not part of this chat, so no block goes.");
|
Assert.That(thread.Blocks, Has.Count.EqualTo(2), "The message to edit is not part of this chat, so no block goes.");
|
||||||
Assert.That(thread.Draft.IsEmpty, Is.True, "A message this chat does not hold never becomes its draft.");
|
Assert.That(thread.Draft.IsEmpty, Is.True, "A message this chat does not hold never becomes its draft.");
|
||||||
Assert.That(File.Exists(transcript.FilePath), Is.True);
|
Assert.That(File.Exists(transcript.FilePath), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ public sealed class ChatThreadFilePathTests
|
|||||||
chat.RewriteFilePaths(Move);
|
chat.RewriteFilePaths(Move);
|
||||||
|
|
||||||
var message = (ContentText)chat.Blocks[0].Content!;
|
var message = (ContentText)chat.Blocks[0].Content!;
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(message.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { New("report.pdf"), New("meeting.md"), New("chart.png") }));
|
Assert.That(message.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { New("report.pdf"), New("meeting.md"), New("chart.png") }));
|
||||||
Assert.That(photo.Source, Is.EqualTo(New("photo.jpg")), "The model gets such an image from its path.");
|
Assert.That(photo.Source, Is.EqualTo(New("photo.jpg")), "The model gets such an image from its path.");
|
||||||
Assert.That(chat.Draft.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { New("call.md") }), "The draft is the one place every path rewrite once forgot.");
|
Assert.That(chat.Draft.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { New("call.md") }), "The draft is the one place every path rewrite once forgot.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -60,11 +60,11 @@ public sealed class ChatThreadFilePathTests
|
|||||||
|
|
||||||
chat.RewriteFilePaths(Move);
|
chat.RewriteFilePaths(Move);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(((ContentText)chat.Blocks[0].Content!).FileAttachments.Single(), Is.SameAs(elsewhere));
|
Assert.That(((ContentText)chat.Blocks[0].Content!).FileAttachments.Single(), Is.SameAs(elsewhere));
|
||||||
Assert.That(linked.Source, Is.EqualTo("https://example.org/chart.png"), "Only an image held by a local path has a path to rewrite.");
|
Assert.That(linked.Source, Is.EqualTo("https://example.org/chart.png"), "Only an image held by a local path has a path to rewrite.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -78,14 +78,14 @@ public sealed class ChatThreadFilePathTests
|
|||||||
var movedPicture = picture.WithFilePath(New("chart.png"));
|
var movedPicture = picture.WithFilePath(New("chart.png"));
|
||||||
var movedDocument = document.WithFilePath(New("report.pdf"));
|
var movedDocument = document.WithFilePath(New("report.pdf"));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(movedTranscript, Is.EqualTo(new ManagedTranscriptAttachment("meeting.md", New("meeting.md"), 512, "meeting.mp3", true)), "A transcript has to stay a transcript, or deleting the chat leaves its file behind.");
|
Assert.That(movedTranscript, Is.EqualTo(new ManagedTranscriptAttachment("meeting.md", New("meeting.md"), 512, "meeting.mp3", true)), "A transcript has to stay a transcript, or deleting the chat leaves its file behind.");
|
||||||
Assert.That(movedPicture, Is.TypeOf<FileAttachmentImage>());
|
Assert.That(movedPicture, Is.TypeOf<FileAttachmentImage>());
|
||||||
Assert.That(((FileAttachmentImage)movedPicture).Source, Is.EqualTo(New("chart.png")), "The model gets the image from its source, not from its path.");
|
Assert.That(((FileAttachmentImage)movedPicture).Source, Is.EqualTo(New("chart.png")), "The model gets the image from its source, not from its path.");
|
||||||
Assert.That(movedPicture.FilePathAsUrl, Is.EqualTo(new FileAttachmentImage("chart.png", New("chart.png"), 2048).FilePathAsUrl), "The preview of the image loads it from there.");
|
Assert.That(movedPicture.FilePathAsUrl, Is.EqualTo(new FileAttachmentImage("chart.png", New("chart.png"), 2048).FilePathAsUrl), "The preview of the image loads it from there.");
|
||||||
Assert.That(movedDocument, Is.EqualTo(new FileAttachment(FileAttachmentType.DOCUMENT, "report.pdf", New("report.pdf"), 1024)));
|
Assert.That(movedDocument, Is.EqualTo(new FileAttachment(FileAttachmentType.DOCUMENT, "report.pdf", New("report.pdf"), 1024)));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Old(string fileName) => Path.Combine(OLD_DIRECTORY, fileName);
|
private static string Old(string fileName) => Path.Combine(OLD_DIRECTORY, fileName);
|
||||||
|
|||||||
@@ -100,12 +100,12 @@ public sealed class ChatThreadOutboundDataRestrictionTests
|
|||||||
[TestCase(OutboundDataRestriction.UNRESTRICTED)]
|
[TestCase(OutboundDataRestriction.UNRESTRICTED)]
|
||||||
public void ARestrictionThisVersionDoesNotKnowCountsAsTheStrictest(OutboundDataRestriction known)
|
public void ARestrictionThisVersionDoesNotKnowCountsAsTheStrictest(OutboundDataRestriction known)
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(UNKNOWN_RESTRICTION.StricterOf(known), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
Assert.That(UNKNOWN_RESTRICTION.StricterOf(known), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
||||||
Assert.That(known.StricterOf(UNKNOWN_RESTRICTION), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
Assert.That(known.StricterOf(UNKNOWN_RESTRICTION), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
||||||
Assert.That(UNKNOWN_RESTRICTION.StricterOf(UNKNOWN_RESTRICTION), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
Assert.That(UNKNOWN_RESTRICTION.StricterOf(UNKNOWN_RESTRICTION), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public sealed class ChatThreadReportedHistoryTests
|
|||||||
|
|
||||||
var history = thread.ReportedHistoryFor(MODEL);
|
var history = thread.ReportedHistoryFor(MODEL);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(history.IsKnown, Is.True);
|
Assert.That(history.IsKnown, Is.True);
|
||||||
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
||||||
@@ -38,7 +38,7 @@ public sealed class ChatThreadReportedHistoryTests
|
|||||||
// would include the reasoning the next request never carries:
|
// would include the reasoning the next request never carries:
|
||||||
//
|
//
|
||||||
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
|
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -54,12 +54,12 @@ public sealed class ChatThreadReportedHistoryTests
|
|||||||
var thread = Thread(Question(1), Block(ChatRole.AI, reasoningOnly, 2));
|
var thread = Thread(Question(1), Block(ChatRole.AI, reasoningOnly, 2));
|
||||||
|
|
||||||
var history = thread.ReportedHistoryFor(MODEL);
|
var history = thread.ReportedHistoryFor(MODEL);
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(history.IsKnown, Is.True);
|
Assert.That(history.IsKnown, Is.True);
|
||||||
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
||||||
Assert.That(history.LastAnswer, Is.Empty);
|
Assert.That(history.LastAnswer, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -135,12 +135,12 @@ public sealed class ChatThreadReportedHistoryTests
|
|||||||
thread.Remove(lastAnswer.Content!);
|
thread.Remove(lastAnswer.Content!);
|
||||||
|
|
||||||
var history = thread.ReportedHistoryFor(MODEL);
|
var history = thread.ReportedHistoryFor(MODEL);
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(history.IsKnown, Is.True, "The first answer is the last block again, with exactly the blocks it was reported for.");
|
Assert.That(history.IsKnown, Is.True, "The first answer is the last block again, with exactly the blocks it was reported for.");
|
||||||
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
||||||
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
|
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -152,12 +152,12 @@ public sealed class ChatThreadReportedHistoryTests
|
|||||||
thread.RollBackTo(firstAnswer.Content!);
|
thread.RollBackTo(firstAnswer.Content!);
|
||||||
|
|
||||||
var history = thread.ReportedHistoryFor(MODEL);
|
var history = thread.ReportedHistoryFor(MODEL);
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(history.IsKnown, Is.True);
|
Assert.That(history.IsKnown, Is.True);
|
||||||
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
Assert.That(history.PromptTokens, Is.EqualTo(1200));
|
||||||
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
|
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ChatThread Thread(params ContentBlock[] blocks) => new()
|
private static ChatThread Thread(params ContentBlock[] blocks) => new()
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ public sealed class ChatThreadRestrictionsOfTests
|
|||||||
|
|
||||||
assistantThread.RequireRestrictionsOf(researchThread);
|
assistantThread.RequireRestrictionsOf(researchThread);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
||||||
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED));
|
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED));
|
||||||
Assert.That(assistantThread.RequiredOutboundDataRestriction.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT));
|
Assert.That(assistantThread.RequiredOutboundDataRestriction.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT));
|
||||||
Assert.That(assistantThread.RequiredOutboundDataRestriction.Reference, Is.EqualTo(DATA_SOURCE_ID));
|
Assert.That(assistantThread.RequiredOutboundDataRestriction.Reference, Is.EqualTo(DATA_SOURCE_ID));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -60,12 +60,12 @@ public sealed class ChatThreadRestrictionsOfTests
|
|||||||
|
|
||||||
assistantThread.RequireRestrictionsOf(researchThread);
|
assistantThread.RequireRestrictionsOf(researchThread);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
|
||||||
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED));
|
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED));
|
||||||
Assert.That(assistantThread.RequiredOutboundDataRestriction.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
Assert.That(assistantThread.RequiredOutboundDataRestriction.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -75,12 +75,12 @@ public sealed class ChatThreadRestrictionsOfTests
|
|||||||
|
|
||||||
assistantThread.RequireRestrictionsOf(new ChatThread());
|
assistantThread.RequireRestrictionsOf(new ChatThread());
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.NONE));
|
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.NONE));
|
||||||
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.NOT_SPECIFIED));
|
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.NOT_SPECIFIED));
|
||||||
Assert.That(assistantThread.RequiredOutboundDataRestriction, Is.EqualTo(OutboundDataRequirement.NONE));
|
Assert.That(assistantThread.RequiredOutboundDataRestriction, Is.EqualTo(OutboundDataRequirement.NONE));
|
||||||
Assert.That(assistantThread.WebAddressesWrittenByModels, Is.Empty);
|
Assert.That(assistantThread.WebAddressesWrittenByModels, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,11 +41,11 @@ public sealed class ChatThreadRollbackTests
|
|||||||
|
|
||||||
var rolledBack = thread.RollBackTo(answer.Content!);
|
var rolledBack = thread.RollBackTo(answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(rolledBack, Is.True, "Two blocks came after the answer, so the rollback removed something.");
|
Assert.That(rolledBack, Is.True, "Two blocks came after the answer, so the rollback removed something.");
|
||||||
Assert.That(Texts(thread), Is.EqualTo(new[] { "First question", "First answer" }), "The hidden prompt goes with the answer to it, although the user never saw it.");
|
Assert.That(Texts(thread), Is.EqualTo(new[] { "First question", "First answer" }), "The hidden prompt goes with the answer to it, although the user never saw it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -117,7 +117,7 @@ public sealed class ChatThreadRollbackTests
|
|||||||
|
|
||||||
thread.RollBackTo(answer.Content!);
|
thread.RollBackTo(answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.AugmentedData, Is.Empty, "Nobody knows whether the retrieved data belongs to a kept or to a removed message, so it must not reach the next system prompt.");
|
Assert.That(thread.AugmentedData, Is.Empty, "Nobody knows whether the retrieved data belongs to a kept or to a removed message, so it must not reach the next system prompt.");
|
||||||
Assert.That(thread.AISelectedDataSources, Is.Empty, "The data sources an agent picked belong to the same retrieval as the data.");
|
Assert.That(thread.AISelectedDataSources, Is.Empty, "The data sources an agent picked belong to the same retrieval as the data.");
|
||||||
@@ -125,7 +125,7 @@ public sealed class ChatThreadRollbackTests
|
|||||||
Assert.That(options.DisableDataSources, Is.False);
|
Assert.That(options.DisableDataSources, Is.False);
|
||||||
Assert.That(options.AutomaticDataSourceSelection, Is.True);
|
Assert.That(options.AutomaticDataSourceSelection, Is.True);
|
||||||
Assert.That(options.PreselectedDataSourceIds, Is.EqualTo(new[] { "handbook" }));
|
Assert.That(options.PreselectedDataSourceIds, Is.EqualTo(new[] { "handbook" }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -150,12 +150,12 @@ public sealed class ChatThreadRollbackTests
|
|||||||
|
|
||||||
thread.RollBackTo(answer.Content!);
|
thread.RollBackTo(answer.Content!);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED), "The confidential data was seen by this chat, so no cloud provider may continue it.");
|
Assert.That(thread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED), "The confidential data was seen by this chat, so no cloud provider may continue it.");
|
||||||
Assert.That(thread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH), "The confidence the data demanded stays, although the message which brought it in is gone.");
|
Assert.That(thread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH), "The confidence the data demanded stays, although the message which brought it in is gone.");
|
||||||
Assert.That(thread.RequiredOutboundDataRestriction, Is.EqualTo(restriction), "The mail content was read by this chat, so it may still carry it out no further than before.");
|
Assert.That(thread.RequiredOutboundDataRestriction, Is.EqualTo(restriction), "The mail content was read by this chat, so it may still carry it out no further than before.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -194,13 +194,13 @@ public sealed class ChatThreadRollbackTests
|
|||||||
|
|
||||||
private static void AssertUnchanged(ChatThread thread, bool rolledBack, params string[] expectedTexts)
|
private static void AssertUnchanged(ChatThread thread, bool rolledBack, params string[] expectedTexts)
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(rolledBack, Is.False, "Nothing came after the content, so there was nothing to roll back.");
|
Assert.That(rolledBack, Is.False, "Nothing came after the content, so there was nothing to roll back.");
|
||||||
Assert.That(Texts(thread), Is.EqualTo(expectedTexts), "A rollback without anything to remove leaves the blocks alone.");
|
Assert.That(Texts(thread), Is.EqualTo(expectedTexts), "A rollback without anything to remove leaves the blocks alone.");
|
||||||
Assert.That(thread.AugmentedData, Is.EqualTo("A chunk from the handbook"), "A rollback without anything to remove keeps the last retrieval, because it still belongs to the last message.");
|
Assert.That(thread.AugmentedData, Is.EqualTo("A chunk from the handbook"), "A rollback without anything to remove keeps the last retrieval, because it still belongs to the last message.");
|
||||||
Assert.That(thread.AISelectedDataSources, Has.Count.EqualTo(1));
|
Assert.That(thread.AISelectedDataSources, Has.Count.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ContentBlock Block(ChatRole role, string text, int minute, bool hidden = false) => new()
|
private static ContentBlock Block(ChatRole role, string text, int minute, bool hidden = false) => new()
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ public sealed class ChatThreadWebAddressTests
|
|||||||
{
|
{
|
||||||
var thread = Thread(Block(ChatRole.USER, "Please summarize https://example.org/report for me.", 1));
|
var thread = Thread(Block(ChatRole.USER, "Please summarize https://example.org/report for me.", 1));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/report")), Is.True);
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/report")), Is.True);
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://EXAMPLE.org/report#summary")), Is.True, "The server gets the same request.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://EXAMPLE.org/report#summary")), Is.True, "The server gets the same request.");
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/report?budget=2026")), Is.False, "The query would carry something the user never wrote.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/report?budget=2026")), Is.False, "The query would carry something the user never wrote.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -43,12 +43,12 @@ public sealed class ChatThreadWebAddressTests
|
|||||||
// An internal service such as a simple Python web server for the results of a data science run:
|
// An internal service such as a simple Python web server for the results of a data science run:
|
||||||
var thread = Thread(Block(ChatRole.USER, "The results of run 17 are on http://10.20.30.40:8000/run-17/, please compare them with run 16.", 1));
|
var thread = Thread(Block(ChatRole.USER, "The results of run 17 are on http://10.20.30.40:8000/run-17/, please compare them with run 16.", 1));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("http://10.20.30.40:8000/run-17/")), Is.True);
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("http://10.20.30.40:8000/run-17/")), Is.True);
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("http://10.20.30.40:8000/run-16/")), Is.False, "The user named run 16, but not its address.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("http://10.20.30.40:8000/run-16/")), Is.False, "The user named run 16, but not its address.");
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://10.20.30.40:8000/run-17/")), Is.False, "Another scheme is another request.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://10.20.30.40:8000/run-17/")), Is.False, "Another scheme is another request.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -89,11 +89,11 @@ public sealed class ChatThreadWebAddressTests
|
|||||||
using var rustService = new RustService("1", "unused");
|
using var rustService = new RustService("1", "unused");
|
||||||
thread.PrepareSystemPrompt(new SettingsManager(NullLogger<SettingsManager>.Instance, rustService));
|
thread.PrepareSystemPrompt(new SettingsManager(NullLogger<SettingsManager>.Instance, rustService));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://handbook.example.org/")), Is.True);
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://handbook.example.org/")), Is.True);
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://intranet.example.org/travel")), Is.True, "The RAG process found it in a data source, the model did not choose it.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://intranet.example.org/travel")), Is.True, "The RAG process found it in a data source, the model did not choose it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -131,11 +131,11 @@ public sealed class ChatThreadWebAddressTests
|
|||||||
var thread = Thread(Block(ChatRole.USER, "Make slides of this: the details are on https://example.org/chosen, and our handbook is on https://example.org/handbook.", 1));
|
var thread = Thread(Block(ChatRole.USER, "Make slides of this: the details are on https://example.org/chosen, and our handbook is on https://example.org/handbook.", 1));
|
||||||
thread.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("The details are on https://example.org/chosen."));
|
thread.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("The details are on https://example.org/chosen."));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.False, "A model wrote it, so it could carry anything of the chat it came from.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.False, "A model wrote it, so it could carry anything of the chat it came from.");
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/handbook")), Is.True, "Another address in the same message still counts.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/handbook")), Is.True, "Another address in the same message still counts.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -151,11 +151,11 @@ public sealed class ChatThreadWebAddressTests
|
|||||||
thread.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("See https://example.org/chosen and https://example.org/sources/2026."));
|
thread.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("See https://example.org/chosen and https://example.org/sources/2026."));
|
||||||
thread.RuntimeWebAddressesFromTools.Add(WebAddresses.CreateRequestKey(new Uri("https://example.org/chosen")));
|
thread.RuntimeWebAddressesFromTools.Add(WebAddresses.CreateRequestKey(new Uri("https://example.org/chosen")));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.True, "A tool returned it in this chat, so it was not only a model which wrote it.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.True, "A tool returned it in this chat, so it was not only a model which wrote it.");
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/sources/2026")), Is.True, "A document the user attached holds it.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/sources/2026")), Is.True, "A document the user attached holds it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -177,11 +177,11 @@ public sealed class ChatThreadWebAddressTests
|
|||||||
|
|
||||||
copy.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("See https://example.org/chosen."));
|
copy.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("See https://example.org/chosen."));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(copy.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.False);
|
Assert.That(copy.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.False);
|
||||||
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.True, "What the copy took over must not reach the chat it was copied from.");
|
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.True, "What the copy took over must not reach the chat it was copied from.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ChatThread Thread(params ContentBlock[] blocks) => new() { Blocks = [..blocks] };
|
private static ChatThread Thread(params ContentBlock[] blocks) => new() { Blocks = [..blocks] };
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ public sealed class ContentBlockApartFromChatTests
|
|||||||
|
|
||||||
var template = new ChatThread { ChatId = Guid.NewGuid(), Blocks = chat.Blocks.Select(block => block.CloneApartFromChat()).ToList() };
|
var template = new ChatThread { ChatId = Guid.NewGuid(), Blocks = chat.Blocks.Select(block => block.CloneApartFromChat()).ToList() };
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(template.CollectProvenanceIds(), Is.Empty, "The entries belong to the chat and are deleted with it.");
|
Assert.That(template.CollectProvenanceIds(), Is.Empty, "The entries belong to the chat and are deleted with it.");
|
||||||
Assert.That(template.Blocks.Select(block => ((ContentText)block.Content!).Text), Is.EqualTo(new[] { "Summarize the minutes.", "The board met on Monday." }));
|
Assert.That(template.Blocks.Select(block => ((ContentText)block.Content!).Text), Is.EqualTo(new[] { "Summarize the minutes.", "The board met on Monday." }));
|
||||||
Assert.That(template.Blocks[0].Content!.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/tmp/minutes.txt" }), "The file is read anew by every chat it reaches.");
|
Assert.That(template.Blocks[0].Content!.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/tmp/minutes.txt" }), "The file is read anew by every chat it reaches.");
|
||||||
Assert.That(chat.CollectProvenanceIds(), Has.Count.EqualTo(2), "The chat itself keeps what it received.");
|
Assert.That(chat.CollectProvenanceIds(), Has.Count.EqualTo(2), "The chat itself keeps what it received.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -15,11 +15,11 @@ public sealed class ContentPresentationTests
|
|||||||
|
|
||||||
presentation.Data = JsonSerializer.SerializeToElement(new { heading = "Second" });
|
presentation.Data = JsonSerializer.SerializeToElement(new { heading = "Second" });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(firstHashCode, Is.Not.Zero);
|
Assert.That(firstHashCode, Is.Not.Zero);
|
||||||
Assert.That(presentation.GetDataHashCode(), Is.Not.EqualTo(firstHashCode));
|
Assert.That(presentation.GetDataHashCode(), Is.Not.EqualTo(firstHashCode));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -53,12 +53,12 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(thread, "You are helpful.", "And of Italy?", null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(thread, "You are helpful.", "And of Italy?", null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.EqualTo(new[] { "You are helpful.", "What is the capital of France?", "Paris." }));
|
Assert.That(parts.Texts, Is.EqualTo(new[] { "You are helpful.", "What is the capital of France?", "Paris." }));
|
||||||
Assert.That(parts.DraftText, Is.EqualTo("And of Italy?"));
|
Assert.That(parts.DraftText, Is.EqualTo("And of Italy?"));
|
||||||
Assert.That(parts.GrowingTexts, Is.Empty, "The draft is a part of its own, and it stands nowhere else.");
|
Assert.That(parts.GrowingTexts, Is.Empty, "The draft is a part of its own, and it stands nowhere else.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -75,12 +75,12 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(thread, string.Empty, "a draft", null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(thread, string.Empty, "a draft", null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.EqualTo(new[] { "A question." }));
|
Assert.That(parts.Texts, Is.EqualTo(new[] { "A question." }));
|
||||||
Assert.That(parts.GrowingTexts, Is.EqualTo(new[] { "The answer so far" }));
|
Assert.That(parts.GrowingTexts, Is.EqualTo(new[] { "The answer so far" }));
|
||||||
Assert.That(parts.DraftText, Is.EqualTo("a draft"));
|
Assert.That(parts.DraftText, Is.EqualTo("a draft"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -91,11 +91,11 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(new() { Blocks = [finished] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(new() { Blocks = [finished] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.EqualTo(new[] { "The whole answer." }));
|
Assert.That(parts.Texts, Is.EqualTo(new[] { "The whole answer." }));
|
||||||
Assert.That(parts.GrowingTexts, Is.Empty);
|
Assert.That(parts.GrowingTexts, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -134,11 +134,11 @@ public sealed class ConversationPartsTests
|
|||||||
{
|
{
|
||||||
var parts = ConversationParts.Of(null, string.Empty, "Hello", null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(null, string.Empty, "Hello", null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.Empty);
|
Assert.That(parts.Texts, Is.Empty);
|
||||||
Assert.That(parts.DraftText, Is.EqualTo("Hello"));
|
Assert.That(parts.DraftText, Is.EqualTo("Hello"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("")]
|
[TestCase("")]
|
||||||
@@ -147,12 +147,12 @@ public sealed class ConversationPartsTests
|
|||||||
{
|
{
|
||||||
var parts = ConversationParts.Of(null, string.Empty, draft, null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(null, string.Empty, draft, null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.Empty);
|
Assert.That(parts.Texts, Is.Empty);
|
||||||
Assert.That(parts.GrowingTexts, Is.Empty);
|
Assert.That(parts.GrowingTexts, Is.Empty);
|
||||||
Assert.That(parts.DraftText, Is.Empty);
|
Assert.That(parts.DraftText, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -169,11 +169,11 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(new() { Blocks = [empty] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(new() { Blocks = [empty] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.Empty);
|
Assert.That(parts.Texts, Is.Empty);
|
||||||
Assert.That(parts.Documents, Is.Empty);
|
Assert.That(parts.Documents, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -191,11 +191,11 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(new() { Blocks = [block] }, string.Empty, "And this one.", [FileAttachment.FromPath(draft)], imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(new() { Blocks = [block] }, string.Empty, "And this one.", [FileAttachment.FromPath(draft)], imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Documents.Select(document => document.FileName), Is.EqualTo(new[] { "older.txt" }));
|
Assert.That(parts.Documents.Select(document => document.FileName), Is.EqualTo(new[] { "older.txt" }));
|
||||||
Assert.That(parts.DraftDocuments.Select(document => document.FileName), Is.EqualTo(new[] { "draft.txt" }), "Both count, each in the part it belongs to.");
|
Assert.That(parts.DraftDocuments.Select(document => document.FileName), Is.EqualTo(new[] { "draft.txt" }), "Both count, each in the part it belongs to.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -219,11 +219,11 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(null, string.Empty, "Look", [FileAttachment.FromPath(document), FileAttachment.FromPath(image)], imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(null, string.Empty, "Look", [FileAttachment.FromPath(document), FileAttachment.FromPath(image)], imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.DraftDocuments.Select(entry => entry.FileName), Is.EqualTo(new[] { "notes.txt" }));
|
Assert.That(parts.DraftDocuments.Select(entry => entry.FileName), Is.EqualTo(new[] { "notes.txt" }));
|
||||||
Assert.That(parts.DraftImages, Is.EqualTo(1));
|
Assert.That(parts.DraftImages, Is.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -240,11 +240,11 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(new() { Blocks = [block] }, string.Empty, "And at this.", [FileAttachment.FromPath(waiting)], imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(new() { Blocks = [block] }, string.Empty, "And at this.", [FileAttachment.FromPath(waiting)], imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Images, Is.EqualTo(1));
|
Assert.That(parts.Images, Is.EqualTo(1));
|
||||||
Assert.That(parts.DraftImages, Is.EqualTo(1));
|
Assert.That(parts.DraftImages, Is.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -275,12 +275,12 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(new() { Blocks = [running] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(new() { Blocks = [running] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.Empty);
|
Assert.That(parts.Texts, Is.Empty);
|
||||||
Assert.That(parts.ToolConversation, Is.EqualTo(new[] { "What the web search found.", "What the page said." }));
|
Assert.That(parts.ToolConversation, Is.EqualTo(new[] { "What the web search found.", "What the page said." }));
|
||||||
Assert.That(parts.GrowingTexts, Is.Empty, "The tool conversation is a part of its own, so that its share can be named.");
|
Assert.That(parts.GrowingTexts, Is.Empty, "The tool conversation is a part of its own, so that its share can be named.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -314,11 +314,11 @@ public sealed class ConversationPartsTests
|
|||||||
|
|
||||||
var parts = ConversationParts.Of(new() { Blocks = [answered] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
var parts = ConversationParts.Of(new() { Blocks = [answered] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.EqualTo(new[] { "Here is what I found." }));
|
Assert.That(parts.Texts, Is.EqualTo(new[] { "Here is what I found." }));
|
||||||
Assert.That(parts.ToolConversation, Is.Empty);
|
Assert.That(parts.ToolConversation, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -335,7 +335,7 @@ public sealed class ConversationPartsTests
|
|||||||
Tool("read_web_page", "Reads one page.", """{"type":"string"}"""),
|
Tool("read_web_page", "Reads one page.", """{"type":"string"}"""),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(parts.Texts, Is.EqualTo(new[]
|
Assert.That(parts.Texts, Is.EqualTo(new[]
|
||||||
{
|
{
|
||||||
@@ -344,7 +344,7 @@ public sealed class ConversationPartsTests
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
Assert.That(parts.GrowingTexts, Is.Empty);
|
Assert.That(parts.GrowingTexts, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ public sealed class ConversationTokenTrackerTests
|
|||||||
tracker.Start();
|
tracker.Start();
|
||||||
await Task.Delay(1_000);
|
await Task.Delay(1_000);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(runs, Is.GreaterThanOrEqualTo(2));
|
Assert.That(runs, Is.GreaterThanOrEqualTo(2));
|
||||||
Assert.That(asked, Is.EqualTo(runs));
|
Assert.That(asked, Is.EqualTo(runs));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -155,11 +155,11 @@ public sealed class ConversationTokenTrackerTests
|
|||||||
var afterDisposal = runs;
|
var afterDisposal = runs;
|
||||||
await Task.Delay(400);
|
await Task.Delay(400);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(afterDisposal, Is.GreaterThan(0), "The tracker never ran, so this proves nothing about stopping it.");
|
Assert.That(afterDisposal, Is.GreaterThan(0), "The tracker never ran, so this proves nothing about stopping it.");
|
||||||
Assert.That(runs, Is.EqualTo(afterDisposal));
|
Assert.That(runs, Is.EqualTo(afterDisposal));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -64,11 +64,11 @@ public sealed class ConversationTokensTests
|
|||||||
ImageLimits = new ImageLimits(8, 100),
|
ImageLimits = new ImageLimits(8, 100),
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(counted.TooManyImages, Is.True);
|
Assert.That(counted.TooManyImages, Is.True);
|
||||||
Assert.That(counted.ImageLimits.MaxInOneMessage, Is.EqualTo(8));
|
Assert.That(counted.ImageLimits.MaxInOneMessage, Is.EqualTo(8));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -111,11 +111,11 @@ public sealed class ConversationTokensTests
|
|||||||
|
|
||||||
var estimated = reported with { HistoryIsReported = false };
|
var estimated = reported with { HistoryIsReported = false };
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reported.UncountedImages, Is.EqualTo(1), "The provider counted the two which were sent.");
|
Assert.That(reported.UncountedImages, Is.EqualTo(1), "The provider counted the two which were sent.");
|
||||||
Assert.That(estimated.UncountedImages, Is.EqualTo(3), "Without a report, nobody counted any of them.");
|
Assert.That(estimated.UncountedImages, Is.EqualTo(3), "Without a report, nobody counted any of them.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -133,11 +133,11 @@ public sealed class ConversationTokensTests
|
|||||||
ImageLimits = new ImageLimits(8, null),
|
ImageLimits = new ImageLimits(8, null),
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(counted.UncountedImages, Is.Zero);
|
Assert.That(counted.UncountedImages, Is.Zero);
|
||||||
Assert.That(counted.TooManyImages, Is.True);
|
Assert.That(counted.TooManyImages, Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ public sealed class FileAttachmentSerializationTests
|
|||||||
|
|
||||||
var json = JsonSerializer.Serialize(template, SettingsManager.JSON_OPTIONS);
|
var json = JsonSerializer.Serialize(template, SettingsManager.JSON_OPTIONS);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(json, Does.Contain(FILE_PATH.Replace(@"\", @"\\")), "The attachment has to reach the JSON, or this test checks nothing.");
|
Assert.That(json, Does.Contain(FILE_PATH.Replace(@"\", @"\\")), "The attachment has to reach the JSON, or this test checks nothing.");
|
||||||
Assert.That(json, Does.Contain("chart.png"), "The attachment of the example conversation has to reach the JSON, or this test checks nothing.");
|
Assert.That(json, Does.Contain("chart.png"), "The attachment of the example conversation has to reach the JSON, or this test checks nothing.");
|
||||||
Assert.That(json, Does.Not.Contain("\"Exists\""));
|
Assert.That(json, Does.Not.Contain("\"Exists\""));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -67,11 +67,11 @@ public sealed class FileAttachmentSerializationTests
|
|||||||
|
|
||||||
var read = JsonSerializer.Deserialize<FileAttachment>(LEGACY_JSON, SettingsManager.JSON_OPTIONS)!;
|
var read = JsonSerializer.Deserialize<FileAttachment>(LEGACY_JSON, SettingsManager.JSON_OPTIONS)!;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(read.FileName, Is.EqualTo("report.pdf"));
|
Assert.That(read.FileName, Is.EqualTo("report.pdf"));
|
||||||
Assert.That(read.FilePath, Is.EqualTo("/tmp/report.pdf"));
|
Assert.That(read.FilePath, Is.EqualTo("/tmp/report.pdf"));
|
||||||
Assert.That(read.FileSizeBytes, Is.EqualTo(1024));
|
Assert.That(read.FileSizeBytes, Is.EqualTo(1024));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,12 +27,12 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var found = content.TryGetExportMarkdown(out var markdown);
|
var found = content.TryGetExportMarkdown(out var markdown);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(markdown, Does.EndWith(content.Sources.ToExportMarkdown()), "What the chat shows below the answer is what the file holds below it.");
|
Assert.That(markdown, Does.EndWith(content.Sources.ToExportMarkdown()), "What the chat shows below the answer is what the file holds below it.");
|
||||||
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "h1", "h2", "ListBlock" }), "The answer stays a paragraph of its own; the source list starts under its own heading.");
|
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "h1", "h2", "ListBlock" }), "The answer stays a paragraph of its own; the source list starts under its own heading.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -47,11 +47,11 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var found = content.TryGetExportMarkdown(out var markdown);
|
var found = content.TryGetExportMarkdown(out var markdown);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "Table", "h1", "h2", "ListBlock" }), "The table ends where it ended; the headings below it are not two more rows.");
|
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "Table", "h1", "h2", "ListBlock" }), "The table ends where it ended; the headings below it are not two more rows.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -66,11 +66,11 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var found = content.TryGetExportMarkdown(out var markdown);
|
var found = content.TryGetExportMarkdown(out var markdown);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "ListBlock", "h1", "h2", "ListBlock" }), "Two lists, not one: the sources do not become the fourth point of the answer.");
|
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "ListBlock", "h1", "h2", "ListBlock" }), "Two lists, not one: the sources do not become the fourth point of the answer.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -86,11 +86,11 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var found = content.TryGetExportMarkdown(out var markdown);
|
var found = content.TryGetExportMarkdown(out var markdown);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "FencedCodeBlock", "h1", "h2", "ListBlock" }), "The code block is closed for the model, so the sources stand below it instead of inside it.");
|
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "FencedCodeBlock", "h1", "h2", "ListBlock" }), "The code block is closed for the model, so the sources stand below it instead of inside it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -101,11 +101,11 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var found = content.TryGetExportMarkdown(out var markdown);
|
var found = content.TryGetExportMarkdown(out var markdown);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(markdown, Is.EqualTo(ANSWER.Trim()), "The everyday case: no heading, no empty line, nothing anybody has to explain.");
|
Assert.That(markdown, Is.EqualTo(ANSWER.Trim()), "The everyday case: no heading, no empty line, nothing anybody has to explain.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -115,11 +115,11 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var found = content.TryGetExportMarkdown(out var markdown);
|
var found = content.TryGetExportMarkdown(out var markdown);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(markdown, Is.EqualTo(content.Sources.ToExportMarkdown()), "Nothing above the heading means no empty line above it either.");
|
Assert.That(markdown, Is.EqualTo(content.Sources.ToExportMarkdown()), "Nothing above the heading means no empty line above it either.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -132,11 +132,11 @@ public sealed class IContentExtensionsTests
|
|||||||
Sources = [TOOL_SOURCE],
|
Sources = [TOOL_SOURCE],
|
||||||
};
|
};
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(picture.TryGetExportMarkdown(out var markdown), Is.False, "There is no text document for a picture, so the caller hears no and says so.");
|
Assert.That(picture.TryGetExportMarkdown(out var markdown), Is.False, "There is no text document for a picture, so the caller hears no and says so.");
|
||||||
Assert.That(markdown, Is.Empty, "A file writer must not put an excuse into the file it writes.");
|
Assert.That(markdown, Is.Empty, "A file writer must not put an excuse into the file it writes.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -147,11 +147,11 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var found = content.TryGetMarkdownText(out var markdown);
|
var found = content.TryGetMarkdownText(out var markdown);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(found, Is.True);
|
Assert.That(found, Is.True);
|
||||||
Assert.That(markdown, Is.EqualTo(ANSWER), "Neither trimmed nor extended: whoever reads a table out of a message wants what the model wrote and nothing else.");
|
Assert.That(markdown, Is.EqualTo(ANSWER), "Neither trimmed nor extended: whoever reads a table out of a message wants what the model wrote and nothing else.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -164,11 +164,11 @@ public sealed class IContentExtensionsTests
|
|||||||
|
|
||||||
var tables = FilesOf(content);
|
var tables = FilesOf(content);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(tables, Has.Count.EqualTo(1), "One table in the message, one table offered for it.");
|
Assert.That(tables, Has.Count.EqualTo(1), "One table in the message, one table offered for it.");
|
||||||
Assert.That(content.ToExportContent(tables[0]), Is.EqualTo(tables[0].Content), "A data table has no column a link list would fit into.");
|
Assert.That(content.ToExportContent(tables[0]), Is.EqualTo(tables[0].Content), "A data table has no column a link list would fit into.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -201,12 +201,12 @@ public sealed class IContentExtensionsTests
|
|||||||
var exported = content.ToExportContent(file);
|
var exported = content.ToExportContent(file);
|
||||||
var appended = exported[file.Content.Length..].Trim();
|
var appended = exported[file.Content.Length..].Trim();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(exported, Does.StartWith(file.Content), "The page stays as the model wrote it.");
|
Assert.That(exported, Does.StartWith(file.Content), "The page stays as the model wrote it.");
|
||||||
Assert.That(appended, Does.StartWith("<!--").And.EndWith("-->"), "Below the page stands one comment and nothing a browser would show.");
|
Assert.That(appended, Does.StartWith("<!--").And.EndWith("-->"), "Below the page stands one comment and nothing a browser would show.");
|
||||||
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
|
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -221,12 +221,12 @@ public sealed class IContentExtensionsTests
|
|||||||
var exported = content.ToExportContent(file);
|
var exported = content.ToExportContent(file);
|
||||||
var appended = exported[file.Content.Length..].Trim();
|
var appended = exported[file.Content.Length..].Trim();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(exported, Does.StartWith(file.Content), "The document stays as the model wrote it.");
|
Assert.That(exported, Does.StartWith(file.Content), "The document stays as the model wrote it.");
|
||||||
Assert.That(appended.Split(Environment.NewLine), Has.All.StartWith("%"), "A line LaTeX would read could stop the whole run.");
|
Assert.That(appended.Split(Environment.NewLine), Has.All.StartWith("%"), "A line LaTeX would read could stop the whole run.");
|
||||||
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
|
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -92,11 +92,11 @@ public sealed class ListContentBlockExtensionsTests
|
|||||||
var messages = await BlocksWithAPicture(this.picturePath).BuildMessagesUsingDirectImageUrlAsync(PictureReadingProvider());
|
var messages = await BlocksWithAPicture(this.picturePath).BuildMessagesUsingDirectImageUrlAsync(PictureReadingProvider());
|
||||||
|
|
||||||
var picture = (messages.Single() as MultimodalMessage)!.Content.OfType<SubContentImageUrl>().Single();
|
var picture = (messages.Single() as MultimodalMessage)!.Content.OfType<SubContentImageUrl>().Single();
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(picture.ImageUrl, Is.EqualTo($"data:image/jpeg;base64,{FakeImageRuntime.IMAGE_BASE64}"));
|
Assert.That(picture.ImageUrl, Is.EqualTo($"data:image/jpeg;base64,{FakeImageRuntime.IMAGE_BASE64}"));
|
||||||
Assert.That(this.runtime.RequestedPaths, Is.EqualTo(new[] { new FileInfo(this.picturePath).FullName }));
|
Assert.That(this.runtime.RequestedPaths, Is.EqualTo(new[] { new FileInfo(this.picturePath).FullName }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -49,11 +49,11 @@ public sealed class ManagedTranscriptAttachmentTests
|
|||||||
var chatDirectory = Path.Combine(this.testDirectory, "workspaces", Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
|
var chatDirectory = Path.Combine(this.testDirectory, "workspaces", Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
|
||||||
var transcript = WriteTranscript(ManagedTranscriptAttachment.GetTranscriptDirectory(chatDirectory));
|
var transcript = WriteTranscript(ManagedTranscriptAttachment.GetTranscriptDirectory(chatDirectory));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(transcript), Is.True);
|
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(transcript), Is.True);
|
||||||
Assert.That(File.Exists(transcript.FilePath), Is.False);
|
Assert.That(File.Exists(transcript.FilePath), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -62,11 +62,11 @@ public sealed class ManagedTranscriptAttachmentTests
|
|||||||
var chatDirectory = Path.Combine(this.testDirectory, "workspaces", Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
|
var chatDirectory = Path.Combine(this.testDirectory, "workspaces", Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
|
||||||
var file = WriteTranscript(Path.Combine(chatDirectory, "attachments", "images"));
|
var file = WriteTranscript(Path.Combine(chatDirectory, "attachments", "images"));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(file), Is.False, "Only the transcript folder belongs to the transcripts.");
|
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(file), Is.False, "Only the transcript folder belongs to the transcripts.");
|
||||||
Assert.That(File.Exists(file.FilePath), Is.True);
|
Assert.That(File.Exists(file.FilePath), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -77,11 +77,11 @@ public sealed class ManagedTranscriptAttachmentTests
|
|||||||
{
|
{
|
||||||
var file = WriteTranscript(ManagedTranscriptAttachment.GetTranscriptDirectory(Path.Combine(outsideDirectory, "tempChats", Guid.NewGuid().ToString())));
|
var file = WriteTranscript(ManagedTranscriptAttachment.GetTranscriptDirectory(Path.Combine(outsideDirectory, "tempChats", Guid.NewGuid().ToString())));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(file), Is.False, "The same folders elsewhere belong to somebody else.");
|
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(file), Is.False, "The same folders elsewhere belong to somebody else.");
|
||||||
Assert.That(File.Exists(file.FilePath), Is.True);
|
Assert.That(File.Exists(file.FilePath), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -50,12 +50,12 @@ public sealed class ProvenanceCleanupTests
|
|||||||
var result = await CleanUpAsync(store, Locations(keptChatId), LoaderOf(chat), DateTimeOffset.UtcNow.AddMinutes(1));
|
var result = await CleanUpAsync(store, Locations(keptChatId), LoaderOf(chat), DateTimeOffset.UtcNow.AddMinutes(1));
|
||||||
var entries = await store.Client.GetEntriesAsync([referenced.Value, ofRemovedMessage!.Value, ofDeletedChat!.Value], TOKEN);
|
var entries = await store.Client.GetEntriesAsync([referenced.Value, ofRemovedMessage!.Value, ofDeletedChat!.Value], TOKEN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(entries.Keys, Is.EquivalentTo(new[] { referenced.Value }));
|
Assert.That(entries.Keys, Is.EquivalentTo(new[] { referenced.Value }));
|
||||||
Assert.That(result.Orphaned, Is.EqualTo(1));
|
Assert.That(result.Orphaned, Is.EqualTo(1));
|
||||||
Assert.That(result.Unreferenced, Is.EqualTo(1));
|
Assert.That(result.Unreferenced, Is.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -83,11 +83,11 @@ public sealed class ProvenanceCleanupTests
|
|||||||
var result = await CleanUpAsync(store, Locations(Guid.NewGuid()), UNREADABLE, DateTimeOffset.UtcNow.AddMinutes(1));
|
var result = await CleanUpAsync(store, Locations(Guid.NewGuid()), UNREADABLE, DateTimeOffset.UtcNow.AddMinutes(1));
|
||||||
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(entries, Is.Empty, "No chat on the disk has no id, so nothing would ever remove such an entry.");
|
Assert.That(entries, Is.Empty, "No chat on the disk has no id, so nothing would ever remove such an entry.");
|
||||||
Assert.That(result.Orphaned, Is.EqualTo(1));
|
Assert.That(result.Orphaned, Is.EqualTo(1));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -100,11 +100,11 @@ public sealed class ProvenanceCleanupTests
|
|||||||
var result = await CleanUpAsync(store, Locations(chatId), UNREADABLE, DateTimeOffset.UtcNow.AddMinutes(1));
|
var result = await CleanUpAsync(store, Locations(chatId), UNREADABLE, DateTimeOffset.UtcNow.AddMinutes(1));
|
||||||
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(entries.Keys, Is.EquivalentTo(new[] { entry.Value }), "Nothing tells what an unreadable chat refers to, e.g., one written by a newer version.");
|
Assert.That(entries.Keys, Is.EquivalentTo(new[] { entry.Value }), "Nothing tells what an unreadable chat refers to, e.g., one written by a newer version.");
|
||||||
Assert.That(result.Unreadable, Is.EqualTo(1), "The next start has to read it again.");
|
Assert.That(result.Unreadable, Is.EqualTo(1), "The next start has to read it again.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -119,12 +119,12 @@ public sealed class ProvenanceCleanupTests
|
|||||||
var result = await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
|
var result = await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
|
||||||
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reads, Is.Empty, "Reading every chat at every start costs much on a small computer.");
|
Assert.That(reads, Is.Empty, "Reading every chat at every start costs much on a small computer.");
|
||||||
Assert.That(result.Skipped, Is.EqualTo(1));
|
Assert.That(result.Skipped, Is.EqualTo(1));
|
||||||
Assert.That(entries.Keys, Is.EquivalentTo(new[] { entry.Value }), "Skipping a chat never removes anything.");
|
Assert.That(entries.Keys, Is.EquivalentTo(new[] { entry.Value }), "Skipping a chat never removes anything.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -139,11 +139,11 @@ public sealed class ProvenanceCleanupTests
|
|||||||
await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), DateTimeOffset.UtcNow.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
|
await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), DateTimeOffset.UtcNow.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
|
||||||
var entries = await store.Client.GetEntriesAsync([previewed!.Value], TOKEN);
|
var entries = await store.Client.GetEntriesAsync([previewed!.Value], TOKEN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reads, Is.EquivalentTo(new[] { chatId }), "The last cleanup had to leave alone what was stored after it began.");
|
Assert.That(reads, Is.EquivalentTo(new[] { chatId }), "The last cleanup had to leave alone what was stored after it began.");
|
||||||
Assert.That(entries, Is.Empty);
|
Assert.That(entries, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(60, TestName = "AChatWrittenSinceTheLastCleanupIsRead")]
|
[TestCase(60, TestName = "AChatWrittenSinceTheLastCleanupIsRead")]
|
||||||
@@ -160,11 +160,11 @@ public sealed class ProvenanceCleanupTests
|
|||||||
await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddSeconds(secondsAfterTheLastCleanup));
|
await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddSeconds(secondsAfterTheLastCleanup));
|
||||||
var entries = await store.Client.GetEntriesAsync([ofRemovedMessage!.Value], TOKEN);
|
var entries = await store.Client.GetEntriesAsync([ofRemovedMessage!.Value], TOKEN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reads, Is.EquivalentTo(new[] { chatId }));
|
Assert.That(reads, Is.EquivalentTo(new[] { chatId }));
|
||||||
Assert.That(entries, Is.Empty);
|
Assert.That(entries, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -192,12 +192,12 @@ public sealed class ProvenanceCleanupTests
|
|||||||
var result = await CleanUpAsync(store, Locations(), LoaderOf(ChatWith(passageId: null, attachmentId: null), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
|
var result = await CleanUpAsync(store, Locations(), LoaderOf(ChatWith(passageId: null, attachmentId: null), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
|
||||||
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(entries, Is.Empty, "Telling that a chat no longer exists costs no reading.");
|
Assert.That(entries, Is.Empty, "Telling that a chat no longer exists costs no reading.");
|
||||||
Assert.That(result.Orphaned, Is.EqualTo(1));
|
Assert.That(result.Orphaned, Is.EqualTo(1));
|
||||||
Assert.That(reads, Is.Empty);
|
Assert.That(reads, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ChatThread ChatWith(Guid? passageId, Guid? attachmentId, Guid? chatId = null) => new()
|
private static ChatThread ChatWith(Guid? passageId, Guid? attachmentId, Guid? chatId = null) => new()
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ public sealed class ChatComposerStateTests
|
|||||||
|
|
||||||
composer.RestoreFromDraft(draft);
|
composer.RestoreFromDraft(draft);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(composer.UserInput, Is.EqualTo("Summarize the meeting"));
|
Assert.That(composer.UserInput, Is.EqualTo("Summarize the meeting"));
|
||||||
Assert.That(composer.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/documents/agenda.pdf" }));
|
Assert.That(composer.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/documents/agenda.pdf" }));
|
||||||
Assert.That(composer.HasUserDraft, Is.True, "A chat template must not replace the message the user came back to.");
|
Assert.That(composer.HasUserDraft, Is.True, "A chat template must not replace the message the user came back to.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -41,11 +41,11 @@ public sealed class ChatComposerStateTests
|
|||||||
|
|
||||||
composer.RestoreFromDraft(new ChatDraft());
|
composer.RestoreFromDraft(new ChatDraft());
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(composer.UserInput, Is.Empty, "The message of the chat before belongs to that chat.");
|
Assert.That(composer.UserInput, Is.Empty, "The message of the chat before belongs to that chat.");
|
||||||
Assert.That(composer.HasUserDraft, Is.False, "Nobody started a message in this chat, so a chat template may still fill the composer.");
|
Assert.That(composer.HasUserDraft, Is.False, "Nobody started a message in this chat, so a chat template may still fill the composer.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -55,10 +55,10 @@ public sealed class ChatComposerStateTests
|
|||||||
|
|
||||||
composer.ApplyTemplate(new ChatTemplate { PredefinedUserPrompt = "Translate the following text:" });
|
composer.ApplyTemplate(new ChatTemplate { PredefinedUserPrompt = "Translate the following text:" });
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(composer.UserInput, Is.EqualTo("Translate the following text:"));
|
Assert.That(composer.UserInput, Is.EqualTo("Translate the following text:"));
|
||||||
Assert.That(composer.HasUserDraft, Is.False, "Leaving the chat must not warn about a message the user never wrote.");
|
Assert.That(composer.HasUserDraft, Is.False, "Leaving the chat must not warn about a message the user never wrote.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,11 +132,11 @@ public sealed class ConfigurationFileExclusionsTests
|
|||||||
component.LockField = locked;
|
component.LockField = locked;
|
||||||
await component.ReplaceAsync("bin/;*.tmp");
|
await component.ReplaceAsync("bin/;*.tmp");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(component.Draft, Is.EqualTo("*.original"));
|
Assert.That(component.Draft, Is.EqualTo("*.original"));
|
||||||
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -155,11 +155,11 @@ public sealed class ConfigurationFileExclusionsTests
|
|||||||
component.Edit("old/;:text:");
|
component.Edit("old/;:text:");
|
||||||
await component.ReplaceAsync("bin/;:text:");
|
await component.ReplaceAsync("bin/;:text:");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(component.Draft, Is.EqualTo("bin/;:text:"));
|
Assert.That(component.Draft, Is.EqualTo("bin/;:text:"));
|
||||||
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
Assert.That(component.StoredText, Is.EqualTo("*.original"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ public sealed class ContentViewerTests
|
|||||||
|
|
||||||
var shown = ContentViewer.CutAtLineBreak(TEXT, 100, out var cutOffCharacters);
|
var shown = ContentViewer.CutAtLineBreak(TEXT, 100, out var cutOffCharacters);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(shown, Is.SameAs(TEXT));
|
Assert.That(shown, Is.SameAs(TEXT));
|
||||||
Assert.That(cutOffCharacters, Is.Zero);
|
Assert.That(cutOffCharacters, Is.Zero);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -38,11 +38,11 @@ public sealed class ContentViewerTests
|
|||||||
|
|
||||||
var shown = ContentViewer.CutAtLineBreak(text, 50, out var cutOffCharacters);
|
var shown = ContentViewer.CutAtLineBreak(text, 50, out var cutOffCharacters);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(shown, Is.EqualTo(text));
|
Assert.That(shown, Is.EqualTo(text));
|
||||||
Assert.That(cutOffCharacters, Is.Zero);
|
Assert.That(cutOffCharacters, Is.Zero);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -52,11 +52,11 @@ public sealed class ContentViewerTests
|
|||||||
|
|
||||||
var shown = ContentViewer.CutAtLineBreak(TEXT, 25, out var cutOffCharacters);
|
var shown = ContentViewer.CutAtLineBreak(TEXT, 25, out var cutOffCharacters);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(shown, Is.EqualTo("| a | b |\n| 1 | 2 |\n"), "The last table row would be torn apart, so it goes as a whole.");
|
Assert.That(shown, Is.EqualTo("| a | b |\n| 1 | 2 |\n"), "The last table row would be torn apart, so it goes as a whole.");
|
||||||
Assert.That(cutOffCharacters, Is.EqualTo(TEXT.Length - shown.Length));
|
Assert.That(cutOffCharacters, Is.EqualTo(TEXT.Length - shown.Length));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -69,11 +69,11 @@ public sealed class ContentViewerTests
|
|||||||
const string TEXT = "The budget $\\rho$ is spent on the hardest tokens.";
|
const string TEXT = "The budget $\\rho$ is spent on the hardest tokens.";
|
||||||
static bool HasMath(Markdig.MarkdownPipeline pipeline) => Markdig.Markdown.Parse(TEXT, pipeline).Descendants<MathInline>().Any();
|
static bool HasMath(Markdig.MarkdownPipeline pipeline) => Markdig.Markdown.Parse(TEXT, pipeline).Descendants<MathInline>().Any();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(HasMath(Markdown.SAFE_MARKDOWN_PIPELINE), Is.True, "The safe pipeline reads this as math.");
|
Assert.That(HasMath(Markdown.SAFE_MARKDOWN_PIPELINE), Is.True, "The safe pipeline reads this as math.");
|
||||||
Assert.That(HasMath(Markdown.CONTENT_MARKDOWN_PIPELINE), Is.False, "What AI Studio read from a document stays text.");
|
Assert.That(HasMath(Markdown.CONTENT_MARKDOWN_PIPELINE), Is.False, "What AI Studio read from a document stays text.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -83,10 +83,10 @@ public sealed class ContentViewerTests
|
|||||||
|
|
||||||
var shown = ContentViewer.CutAtLineBreak(text, 20, out var cutOffCharacters);
|
var shown = ContentViewer.CutAtLineBreak(text, 20, out var cutOffCharacters);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(shown, Has.Length.EqualTo(20));
|
Assert.That(shown, Has.Length.EqualTo(20));
|
||||||
Assert.That(cutOffCharacters, Is.EqualTo(10));
|
Assert.That(cutOffCharacters, Is.EqualTo(10));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,12 +24,12 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
{
|
{
|
||||||
var state = Create(text);
|
var state = Create(text);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.FolderText, Is.EqualTo("node_modules/; bin/"));
|
Assert.That(state.FolderText, Is.EqualTo("node_modules/; bin/"));
|
||||||
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
|
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
|
||||||
Assert.That(state.Text, Is.EqualTo(text));
|
Assert.That(state.Text, Is.EqualTo(text));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -38,12 +38,12 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
var state = Create("bin/;*.min.js;draft-*");
|
var state = Create("bin/;*.min.js;draft-*");
|
||||||
state.EditFolders("obj/\nbuild/");
|
state.EditFolders("obj/\nbuild/");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "obj/", "build/", "*.min.js", "draft-*" }));
|
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "obj/", "build/", "*.min.js", "draft-*" }));
|
||||||
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
|
Assert.That(state.FileText, Is.EqualTo("*.min.js; draft-*"));
|
||||||
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.min.js; draft-*"));
|
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.min.js; draft-*"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -52,12 +52,12 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
var state = Create("bin/;node_modules/;*.min.js");
|
var state = Create("bin/;node_modules/;*.min.js");
|
||||||
state.EditFiles("*.wav;*.md");
|
state.EditFiles("*.wav;*.md");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "bin/", "node_modules/", "*.wav", "*.md" }));
|
Assert.That(FileExclusions.Parse(state.Text), Is.EqualTo(new[] { "bin/", "node_modules/", "*.wav", "*.md" }));
|
||||||
Assert.That(state.FolderText, Is.EqualTo("bin/; node_modules/"));
|
Assert.That(state.FolderText, Is.EqualTo("bin/; node_modules/"));
|
||||||
Assert.That(state.Text, Is.EqualTo("bin/; node_modules/; *.wav; *.md"));
|
Assert.That(state.Text, Is.EqualTo("bin/; node_modules/; *.wav; *.md"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("")]
|
[TestCase("")]
|
||||||
@@ -80,7 +80,7 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
state.EditFolders("node_modules/;cmake-build-*/");
|
state.EditFolders("node_modules/;cmake-build-*/");
|
||||||
var patterns = FileExclusions.Parse(state.Text);
|
var patterns = FileExclusions.Parse(state.Text);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(patterns, Is.EqualTo(new[] { "node_modules/", "cmake-build-*/" }));
|
Assert.That(patterns, Is.EqualTo(new[] { "node_modules/", "cmake-build-*/" }));
|
||||||
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
|
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
|
||||||
@@ -88,7 +88,7 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
Assert.That(FileExclusions.ExcludesFolder("node_modules", patterns), Is.True);
|
Assert.That(FileExclusions.ExcludesFolder("node_modules", patterns), Is.True);
|
||||||
Assert.That(FileExclusions.ExcludesFolder("cmake-build-debug", patterns), Is.True);
|
Assert.That(FileExclusions.ExcludesFolder("cmake-build-debug", patterns), Is.True);
|
||||||
Assert.That(FileExclusions.ExcludesFile("node_modules", patterns), Is.False);
|
Assert.That(FileExclusions.ExcludesFile("node_modules", patterns), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(":text:")]
|
[TestCase(":text:")]
|
||||||
@@ -103,11 +103,11 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
else
|
else
|
||||||
state.EditFolders("bin/");
|
state.EditFolders("bin/");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
|
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
|
||||||
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(":text:")]
|
[TestCase(":text:")]
|
||||||
@@ -117,11 +117,11 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
var state = Create(string.Empty);
|
var state = Create(string.Empty);
|
||||||
state.EditFolders(selector);
|
state.EditFolders(selector);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.Text, Is.EqualTo(selector));
|
Assert.That(state.Text, Is.EqualTo(selector));
|
||||||
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -156,12 +156,12 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
else
|
else
|
||||||
state.RestoreFiles(DEFAULTS);
|
state.RestoreFiles(DEFAULTS);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.FolderText, Is.EqualTo(folders ? "bin/; node_modules/" : "latest/; "));
|
Assert.That(state.FolderText, Is.EqualTo(folders ? "bin/; node_modules/" : "latest/; "));
|
||||||
Assert.That(state.FileText, Is.EqualTo(folders ? "*.latest; " : "*.tmp; *.bak"));
|
Assert.That(state.FileText, Is.EqualTo(folders ? "*.latest; " : "*.tmp; *.bak"));
|
||||||
Assert.That(state.Text, Is.EqualTo(folders ? "bin/; node_modules/; *.latest" : "latest/; *.tmp; *.bak"));
|
Assert.That(state.Text, Is.EqualTo(folders ? "bin/; node_modules/; *.latest" : "latest/; *.tmp; *.bak"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
@@ -187,11 +187,11 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
else
|
else
|
||||||
state.RestoreFiles(FileExclusions.DEFAULT_PATTERNS);
|
state.RestoreFiles(FileExclusions.DEFAULT_PATTERNS);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
|
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(invalidRule));
|
||||||
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
Assert.That(FileExclusions.IsValid(state.Text), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -199,13 +199,13 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
{
|
{
|
||||||
var state = Create("bin/;*.min.js");
|
var state = Create("bin/;*.min.js");
|
||||||
state.EditFiles("obj/;*.wav;draft-*");
|
state.EditFiles("obj/;*.wav;draft-*");
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.IsValid, Is.False);
|
Assert.That(state.IsValid, Is.False);
|
||||||
Assert.That(state.Text, Is.EqualTo("bin/; obj/; *.wav; draft-*"));
|
Assert.That(state.Text, Is.EqualTo("bin/; obj/; *.wav; draft-*"));
|
||||||
Assert.That(state.FolderText, Is.EqualTo("bin/"));
|
Assert.That(state.FolderText, Is.EqualTo("bin/"));
|
||||||
Assert.That(state.FileText, Is.EqualTo("obj/;*.wav;draft-*"));
|
Assert.That(state.FileText, Is.EqualTo("obj/;*.wav;draft-*"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
@@ -230,12 +230,12 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
state.Receive(earlierValue);
|
state.Receive(earlierValue);
|
||||||
state.Receive(state.Text);
|
state.Receive(state.Text);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.FolderText, Is.EqualTo("obj/; build/; "));
|
Assert.That(state.FolderText, Is.EqualTo("obj/; build/; "));
|
||||||
Assert.That(state.FileText, Is.EqualTo("*.wav; draft-*; "));
|
Assert.That(state.FileText, Is.EqualTo("*.wav; draft-*; "));
|
||||||
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.wav; draft-*"));
|
Assert.That(state.Text, Is.EqualTo("obj/; build/; *.wav; draft-*"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -247,11 +247,11 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
state.EditFiles("*.latest");
|
state.EditFiles("*.latest");
|
||||||
state.Receive(earlierValue);
|
state.Receive(earlierValue);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.Text, Is.EqualTo("*.latest"));
|
Assert.That(state.Text, Is.EqualTo("*.latest"));
|
||||||
Assert.That(state.FileText, Is.EqualTo("*.latest"));
|
Assert.That(state.FileText, Is.EqualTo("*.latest"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -271,11 +271,11 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
state.EditFiles("*.latest");
|
state.EditFiles("*.latest");
|
||||||
state.Receive("policy/;*.secret");
|
state.Receive("policy/;*.secret");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.FolderText, Is.EqualTo("policy/"));
|
Assert.That(state.FolderText, Is.EqualTo("policy/"));
|
||||||
Assert.That(state.FileText, Is.EqualTo("*.secret"));
|
Assert.That(state.FileText, Is.EqualTo("*.secret"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -311,7 +311,7 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
state.EditFolders(":text:");
|
state.EditFolders(":text:");
|
||||||
state.Reset(FileExclusions.DEFAULT_PATTERNS);
|
state.Reset(FileExclusions.DEFAULT_PATTERNS);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.Text, Is.EqualTo(FileExclusions.DEFAULT_PATTERNS));
|
Assert.That(state.Text, Is.EqualTo(FileExclusions.DEFAULT_PATTERNS));
|
||||||
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
|
Assert.That(FileExclusions.IsValid(state.Text), Is.True);
|
||||||
@@ -319,7 +319,7 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
Assert.That(state.FileText, Does.Contain("*.exe"));
|
Assert.That(state.FileText, Does.Contain("*.exe"));
|
||||||
Assert.That(state.FolderText, Does.Not.Contain("\n").And.Not.Contain("\r"));
|
Assert.That(state.FolderText, Does.Not.Contain("\n").And.Not.Contain("\r"));
|
||||||
Assert.That(state.FileText, Does.Not.Contain("\n").And.Not.Contain("\r"));
|
Assert.That(state.FileText, Does.Not.Contain("\n").And.Not.Contain("\r"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -341,14 +341,14 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
var state = Create("bin/;*.tmp");
|
var state = Create("bin/;*.tmp");
|
||||||
state.EditFolders(pattern);
|
state.EditFolders(pattern);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.IsValid, Is.False);
|
Assert.That(state.IsValid, Is.False);
|
||||||
Assert.That(state.FolderText, Is.EqualTo(pattern));
|
Assert.That(state.FolderText, Is.EqualTo(pattern));
|
||||||
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(pattern));
|
Assert.That(FileExclusions.Parse(state.Text), Does.Contain(pattern));
|
||||||
Assert.That(FileExclusions.Parse(state.Text), Does.Not.Contain(pattern + "/"));
|
Assert.That(FileExclusions.Parse(state.Text), Does.Not.Contain(pattern + "/"));
|
||||||
Assert.That(state.FileText, Is.EqualTo("*.tmp"));
|
Assert.That(state.FileText, Is.EqualTo("*.tmp"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -360,12 +360,12 @@ public sealed class FileExclusionEditorStateTests
|
|||||||
state.EditFiles("*.bak;obj/");
|
state.EditFiles("*.bak;obj/");
|
||||||
state.Receive(earlierValue);
|
state.Receive(earlierValue);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(state.IsValid, Is.False);
|
Assert.That(state.IsValid, Is.False);
|
||||||
Assert.That(state.FileText, Is.EqualTo("*.bak;obj/"));
|
Assert.That(state.FileText, Is.EqualTo("*.bak;obj/"));
|
||||||
Assert.That(state.FolderText, Is.EqualTo("bin/"));
|
Assert.That(state.FolderText, Is.EqualTo("bin/"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FileExclusionEditorState Create(string text)
|
private static FileExclusionEditorState Create(string text)
|
||||||
|
|||||||
@@ -68,11 +68,11 @@ public sealed class FileExclusionEditorTests
|
|||||||
await editor.EditAsync(folders ? "*.keep;wrong/" : "*.wrong", folders: !folders);
|
await editor.EditAsync(folders ? "*.keep;wrong/" : "*.wrong", folders: !folders);
|
||||||
await editor.ResetAsync(folders);
|
await editor.ResetAsync(folders);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(published, Is.Empty);
|
Assert.That(published, Is.Empty);
|
||||||
Assert.That(resets, Is.Empty);
|
Assert.That(resets, Is.Empty);
|
||||||
});
|
}
|
||||||
|
|
||||||
await editor.EditAsync(folders ? "*.keep" : "keep/", folders: !folders);
|
await editor.EditAsync(folders ? "*.keep" : "keep/", folders: !folders);
|
||||||
Assert.That(published, Is.EqualTo(new[] { folders ? "bin/; *.keep" : "keep/; *.tmp" }));
|
Assert.That(published, Is.EqualTo(new[] { folders ? "bin/; *.keep" : "keep/; *.tmp" }));
|
||||||
@@ -90,11 +90,11 @@ public sealed class FileExclusionEditorTests
|
|||||||
await editor.EditAsync(folders ? "*.wrong" : "wrong/", folders);
|
await editor.EditAsync(folders ? "*.wrong" : "wrong/", folders);
|
||||||
await editor.ResetAsync(folders);
|
await editor.ResetAsync(folders);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(published, Is.Empty);
|
Assert.That(published, Is.Empty);
|
||||||
Assert.That(resets, Is.EqualTo(new[] { folders ? "bin/; *.custom" : "custom/; *.tmp" }));
|
Assert.That(resets, Is.EqualTo(new[] { folders ? "bin/; *.custom" : "custom/; *.tmp" }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -114,11 +114,11 @@ public sealed class FileExclusionEditorTests
|
|||||||
await editor.ResetAsync(folders: true);
|
await editor.ResetAsync(folders: true);
|
||||||
await editor.ResetAsync(folders: false);
|
await editor.ResetAsync(folders: false);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(published, Is.Empty);
|
Assert.That(published, Is.Empty);
|
||||||
Assert.That(resets, Is.Empty);
|
Assert.That(resets, Is.Empty);
|
||||||
});
|
}
|
||||||
|
|
||||||
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = default(EventCallback<string>) });
|
editor.SetParameters(new() { [nameof(FileExclusionEditor.ResetRequested)] = default(EventCallback<string>) });
|
||||||
await editor.ResetAsync(folders: true);
|
await editor.ResetAsync(folders: true);
|
||||||
@@ -140,11 +140,11 @@ public sealed class FileExclusionEditorTests
|
|||||||
|
|
||||||
await editor.EditAsync("*.wrong", folders: true);
|
await editor.EditAsync("*.wrong", folders: true);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(editor.IsValid, Is.False);
|
Assert.That(editor.IsValid, Is.False);
|
||||||
Assert.That(published, Is.Empty);
|
Assert.That(published, Is.Empty);
|
||||||
});
|
}
|
||||||
|
|
||||||
await editor.EditAsync("custom/", folders: true);
|
await editor.EditAsync("custom/", folders: true);
|
||||||
Assert.That(editor.IsValid, Is.True);
|
Assert.That(editor.IsValid, Is.True);
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ public sealed class MailFolderPickerTests
|
|||||||
Folder("Archive", specialUse: MailFolderSpecialUse.ARCHIVE),
|
Folder("Archive", specialUse: MailFolderSpecialUse.ARCHIVE),
|
||||||
Folder("INBOX", isInbox: true));
|
Folder("INBOX", isInbox: true));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(root.Value, Is.Empty, "The root does not stand for the whole mailbox.");
|
Assert.That(root.Value, Is.Empty, "The root does not stand for the whole mailbox.");
|
||||||
Assert.That(root.Text, Is.EqualTo(WHOLE_MAILBOX));
|
Assert.That(root.Text, Is.EqualTo(WHOLE_MAILBOX));
|
||||||
Assert.That(ValuesOf(root), Is.EqualTo(new[] { "INBOX", "Archive", "Projects" }), "The top level is not ordered with the inbox first.");
|
Assert.That(ValuesOf(root), Is.EqualTo(new[] { "INBOX", "Archive", "Projects" }), "The top level is not ordered with the inbox first.");
|
||||||
Assert.That(ValuesOf(Child(root, "Projects")), Is.EqualTo(new[] { "Projects/Alpha" }), "The subfolder did not land below its parent.");
|
Assert.That(ValuesOf(Child(root, "Projects")), Is.EqualTo(new[] { "Projects/Alpha" }), "The subfolder did not land below its parent.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -44,11 +44,11 @@ public sealed class MailFolderPickerTests
|
|||||||
Folder("INBOX.Sent", "INBOX", separator: '.', specialUse: MailFolderSpecialUse.SENT),
|
Folder("INBOX.Sent", "INBOX", separator: '.', specialUse: MailFolderSpecialUse.SENT),
|
||||||
Folder("INBOX.Drafts", "INBOX", separator: '.', specialUse: MailFolderSpecialUse.DRAFTS));
|
Folder("INBOX.Drafts", "INBOX", separator: '.', specialUse: MailFolderSpecialUse.DRAFTS));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(ValuesOf(root), Is.EqualTo(new[] { "INBOX" }));
|
Assert.That(ValuesOf(root), Is.EqualTo(new[] { "INBOX" }));
|
||||||
Assert.That(ValuesOf(Child(root, "INBOX")), Is.EqualTo(new[] { "INBOX.Drafts", "INBOX.Sent" }));
|
Assert.That(ValuesOf(Child(root, "INBOX")), Is.EqualTo(new[] { "INBOX.Drafts", "INBOX.Sent" }));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -85,13 +85,13 @@ public sealed class MailFolderPickerTests
|
|||||||
};
|
};
|
||||||
|
|
||||||
var root = MailFolderPicker.BuildTree(folders, "Projects/Alpha/Reports", WHOLE_MAILBOX).Single();
|
var root = MailFolderPicker.BuildTree(folders, "Projects/Alpha/Reports", WHOLE_MAILBOX).Single();
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(root.Expanded, Is.True, "The whole mailbox is collapsed.");
|
Assert.That(root.Expanded, Is.True, "The whole mailbox is collapsed.");
|
||||||
Assert.That(Child(root, "Projects").Expanded, Is.True, "The way to the picked folder is collapsed.");
|
Assert.That(Child(root, "Projects").Expanded, Is.True, "The way to the picked folder is collapsed.");
|
||||||
Assert.That(Child(Child(root, "Projects"), "Projects/Alpha").Expanded, Is.True, "The way to the picked folder is collapsed.");
|
Assert.That(Child(Child(root, "Projects"), "Projects/Alpha").Expanded, Is.True, "The way to the picked folder is collapsed.");
|
||||||
Assert.That(Child(root, "Private").Expanded, Is.False, "A folder off the way to the picked folder is expanded.");
|
Assert.That(Child(root, "Private").Expanded, Is.False, "A folder off the way to the picked folder is expanded.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(MailFolderSpecialUse.NONE, ExpectedResult = true)]
|
[TestCase(MailFolderSpecialUse.NONE, ExpectedResult = true)]
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public sealed class CapabilityTests
|
|||||||
{
|
{
|
||||||
var bits = new Dictionary<ulong, Capability>();
|
var bits = new Dictionary<ulong, Capability>();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
foreach (var capability in Enum.GetValues<Capability>())
|
foreach (var capability in Enum.GetValues<Capability>())
|
||||||
{
|
{
|
||||||
@@ -52,7 +52,7 @@ public sealed class CapabilityTests
|
|||||||
|
|
||||||
bits[value] = capability;
|
bits[value] = capability;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ public sealed class ContextWindowRuleTests
|
|||||||
{
|
{
|
||||||
var window = provider.GetModelProfile(new Model(modelId, null)).Context;
|
var window = provider.GetModelProfile(new Model(modelId, null)).Context;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(window.IsKnown, Is.True);
|
Assert.That(window.IsKnown, Is.True);
|
||||||
Assert.That(window.DefaultTokens, Is.EqualTo(tokens));
|
Assert.That(window.DefaultTokens, Is.EqualTo(tokens));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -70,11 +70,11 @@ public sealed class ContextWindowRuleTests
|
|||||||
//
|
//
|
||||||
var profile = ModelRegistry.Shared.Profile(LLMProviders.OPEN_AI, "gpt-5.3");
|
var profile = ModelRegistry.Shared.Profile(LLMProviders.OPEN_AI, "gpt-5.3");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Context.IsKnown, Is.False);
|
Assert.That(profile.Context.IsKnown, Is.False);
|
||||||
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.True, "Everything else it does inherit.");
|
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.True, "Everything else it does inherit.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -100,10 +100,10 @@ public sealed class ContextWindowRuleTests
|
|||||||
//
|
//
|
||||||
var profile = ModelRegistry.Shared.Profile(LLMProviders.SELF_HOSTED, "some-model-nobody-wrote-a-rule-for");
|
var profile = ModelRegistry.Shared.Profile(LLMProviders.SELF_HOSTED, "some-model-nobody-wrote-a-rule-for");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Context.IsKnown, Is.False);
|
Assert.That(profile.Context.IsKnown, Is.False);
|
||||||
Assert.That(profile.Context.DefaultTokens, Is.Zero, "And the number next to it is meaningless, which is why nothing may read it without asking first.");
|
Assert.That(profile.Context.DefaultTokens, Is.Zero, "And the number next to it is meaningless, which is why nothing may read it without asking first.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,7 @@ public sealed class CorpusTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void EveryKnownWrongAnswerSaysWhyAndWhereThatCanBeChecked()
|
public void EveryKnownWrongAnswerSaysWhyAndWhereThatCanBeChecked()
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
foreach (var change in ExpectedChanges.ENTRIES)
|
foreach (var change in ExpectedChanges.ENTRIES)
|
||||||
{
|
{
|
||||||
@@ -49,7 +49,7 @@ public sealed class CorpusTests
|
|||||||
Assert.That(change.Source, Is.Not.Empty, $"{change.Provider} {change.ModelId} does not say where that can be checked.");
|
Assert.That(change.Source, Is.Not.Empty, $"{change.Provider} {change.ModelId} does not say where that can be checked.");
|
||||||
Assert.That(change.AnswerWanted, Is.Not.Empty, $"{change.Provider} {change.ModelId} does not say what the answer should be.");
|
Assert.That(change.AnswerWanted, Is.Not.Empty, $"{change.Provider} {change.ModelId} does not say what the answer should be.");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -51,12 +51,12 @@ public sealed class ModelPatternLiteralAnalyzerTests
|
|||||||
{
|
{
|
||||||
var reported = await AnalyzeAsync(statements);
|
var reported = await AnalyzeAsync(statements);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reported, Has.Count.EqualTo(1));
|
Assert.That(reported, Has.Count.EqualTo(1));
|
||||||
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0013"));
|
Assert.That(reported.FirstOrDefault()?.Id, Is.EqualTo("MWAIS0013"));
|
||||||
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain($"write it as \"{expectedSpelling}\""));
|
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain($"write it as \"{expectedSpelling}\""));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -64,11 +64,11 @@ public sealed class ModelPatternLiteralAnalyzerTests
|
|||||||
{
|
{
|
||||||
var reported = await AnalyzeAsync("""builder.Rule("___");""");
|
var reported = await AnalyzeAsync("""builder.Rule("___");""");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reported, Has.Count.EqualTo(1));
|
Assert.That(reported, Has.Count.EqualTo(1));
|
||||||
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain("nothing of it survives"));
|
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain("nothing of it survives"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -97,12 +97,12 @@ public sealed class ModelRegistryGeneratorTests
|
|||||||
{
|
{
|
||||||
var generated = Generate(TWO_FAMILIES_AND_A_HOST, out _, out _);
|
var generated = Generate(TWO_FAMILIES_AND_A_HOST, out _, out _);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(generated, Does.Contain("new global::Sample.FirstFamily()"));
|
Assert.That(generated, Does.Contain("new global::Sample.FirstFamily()"));
|
||||||
Assert.That(generated, Does.Contain("new global::Sample.SecondFamily()"), "A family which inherits through another class is still a family.");
|
Assert.That(generated, Does.Contain("new global::Sample.SecondFamily()"), "A family which inherits through another class is still a family.");
|
||||||
Assert.That(generated, Does.Contain("new global::Sample.SampleHost()"));
|
Assert.That(generated, Does.Contain("new global::Sample.SampleHost()"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -142,11 +142,11 @@ public sealed class ModelRegistryGeneratorTests
|
|||||||
//
|
//
|
||||||
var generated = Generate("namespace Sample;\n\npublic sealed class NothingToDoWithModels;", out var updated, out _);
|
var generated = Generate("namespace Sample;\n\npublic sealed class NothingToDoWithModels;", out var updated, out _);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(generated, Does.Contain("public static class ModelRegistrations"));
|
Assert.That(generated, Does.Contain("public static class ModelRegistrations"));
|
||||||
Assert.That(CompilationHarness.ErrorsOf(updated), Is.Empty);
|
Assert.That(CompilationHarness.ErrorsOf(updated), Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -155,12 +155,12 @@ public sealed class ModelRegistryGeneratorTests
|
|||||||
var generated = Generate(A_FAMILY_NEEDING_AN_ARGUMENT, out _, out var diagnostics);
|
var generated = Generate(A_FAMILY_NEEDING_AN_ARGUMENT, out _, out var diagnostics);
|
||||||
var reported = diagnostics.Where(diagnostic => diagnostic.Id is "MDR001").ToList();
|
var reported = diagnostics.Where(diagnostic => diagnostic.Id is "MDR001").ToList();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(reported, Has.Count.EqualTo(1));
|
Assert.That(reported, Has.Count.EqualTo(1));
|
||||||
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain("DemandingFamily"));
|
Assert.That(reported.FirstOrDefault()?.GetMessage(), Does.Contain("DemandingFamily"));
|
||||||
Assert.That(generated, Does.Not.Contain("DemandingFamily"));
|
Assert.That(generated, Does.Not.Contain("DemandingFamily"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -170,11 +170,11 @@ public sealed class ModelRegistryGeneratorTests
|
|||||||
// The tests above run the generator by hand. This one asks whether it also ran while the app
|
// The tests above run the generator by hand. This one asks whether it also ran while the app
|
||||||
// was built, which is a different question and the one that actually matters.
|
// was built, which is a different question and the one that actually matters.
|
||||||
//
|
//
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(ModelRegistrations.CreateFamilies(), Is.Not.Null);
|
Assert.That(ModelRegistrations.CreateFamilies(), Is.Not.Null);
|
||||||
Assert.That(ModelRegistrations.CreateHosts(), Is.Not.Null);
|
Assert.That(ModelRegistrations.CreateHosts(), Is.Not.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string Generate(string source, out Compilation updated, out IReadOnlyList<Diagnostic> diagnostics)
|
private static string Generate(string source, out Compilation updated, out IReadOnlyList<Diagnostic> diagnostics)
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TrySplitOrganization(new ModelId("anthropic/claude-opus-5"), out var inner, out var vendor);
|
var taken = HostNaming.TrySplitOrganization(new ModelId("anthropic/claude-opus-5"), out var inner, out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.True);
|
Assert.That(taken, Is.True);
|
||||||
Assert.That(inner.Original, Is.EqualTo("claude-opus-5"));
|
Assert.That(inner.Original, Is.EqualTo("claude-opus-5"));
|
||||||
Assert.That(vendor, Is.EqualTo(ModelVendor.ANTHROPIC));
|
Assert.That(vendor, Is.EqualTo(ModelVendor.ANTHROPIC));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -37,18 +37,18 @@ public sealed class HostNamingTests
|
|||||||
//
|
//
|
||||||
var taken = HostNaming.TrySplitOrganization(new ModelId("azure/gpt-5.6"), out var inner, out var vendor);
|
var taken = HostNaming.TrySplitOrganization(new ModelId("azure/gpt-5.6"), out var inner, out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.True);
|
Assert.That(taken, Is.True);
|
||||||
Assert.That(inner.Original, Is.EqualTo("gpt-5.6"));
|
Assert.That(inner.Original, Is.EqualTo("gpt-5.6"));
|
||||||
Assert.That(vendor, Is.Null);
|
Assert.That(vendor, Is.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void AnOrganizationIsRecognizedWhicheverWayTheHostSpellsIt()
|
public void AnOrganizationIsRecognizedWhicheverWayTheHostSpellsIt()
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(HostNaming.VendorOfOrganization("meta-llama"), Is.EqualTo(ModelVendor.META));
|
Assert.That(HostNaming.VendorOfOrganization("meta-llama"), Is.EqualTo(ModelVendor.META));
|
||||||
Assert.That(HostNaming.VendorOfOrganization("Qwen"), Is.EqualTo(ModelVendor.ALIBABA));
|
Assert.That(HostNaming.VendorOfOrganization("Qwen"), Is.EqualTo(ModelVendor.ALIBABA));
|
||||||
@@ -57,7 +57,7 @@ public sealed class HostNamingTests
|
|||||||
Assert.That(HostNaming.VendorOfOrganization("zai"), Is.EqualTo(ModelVendor.Z_AI));
|
Assert.That(HostNaming.VendorOfOrganization("zai"), Is.EqualTo(ModelVendor.Z_AI));
|
||||||
Assert.That(HostNaming.VendorOfOrganization("minimaxi"), Is.EqualTo(ModelVendor.MINIMAX));
|
Assert.That(HostNaming.VendorOfOrganization("minimaxi"), Is.EqualTo(ModelVendor.MINIMAX));
|
||||||
Assert.That(HostNaming.VendorOfOrganization("somebody-else"), Is.EqualTo(ModelVendor.UNKNOWN));
|
Assert.That(HostNaming.VendorOfOrganization("somebody-else"), Is.EqualTo(ModelVendor.UNKNOWN));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -65,12 +65,12 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TrySplitOrganization(new ModelId("llama-3.3-70b-versatile"), out var inner, out var vendor);
|
var taken = HostNaming.TrySplitOrganization(new ModelId("llama-3.3-70b-versatile"), out var inner, out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.False);
|
Assert.That(taken, Is.False);
|
||||||
Assert.That(inner.Original, Is.EqualTo("llama-3.3-70b-versatile"));
|
Assert.That(inner.Original, Is.EqualTo("llama-3.3-70b-versatile"));
|
||||||
Assert.That(vendor, Is.Null);
|
Assert.That(vendor, Is.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -82,11 +82,11 @@ public sealed class HostNamingTests
|
|||||||
//
|
//
|
||||||
var taken = HostNaming.TrySplitOrganization(new ModelId("accounts/fireworks/models/llama-v3p1-405b-instruct"), out var inner, out _);
|
var taken = HostNaming.TrySplitOrganization(new ModelId("accounts/fireworks/models/llama-v3p1-405b-instruct"), out var inner, out _);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.True);
|
Assert.That(taken, Is.True);
|
||||||
Assert.That(inner.Original, Is.EqualTo("fireworks/models/llama-v3p1-405b-instruct"));
|
Assert.That(inner.Original, Is.EqualTo("fireworks/models/llama-v3p1-405b-instruct"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -94,11 +94,11 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TrySplitOrganization(new ModelId("openai/"), out var inner, out _);
|
var taken = HostNaming.TrySplitOrganization(new ModelId("openai/"), out var inner, out _);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.False);
|
Assert.That(taken, Is.False);
|
||||||
Assert.That(inner.Original, Is.EqualTo("openai/"));
|
Assert.That(inner.Original, Is.EqualTo("openai/"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -106,11 +106,11 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TryStripRoutingSuffix(new ModelId("google/gemma-4-31B-it:novita"), out var inner);
|
var taken = HostNaming.TryStripRoutingSuffix(new ModelId("google/gemma-4-31B-it:novita"), out var inner);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.True);
|
Assert.That(taken, Is.True);
|
||||||
Assert.That(inner.Original, Is.EqualTo("google/gemma-4-31B-it"));
|
Assert.That(inner.Original, Is.EqualTo("google/gemma-4-31B-it"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -118,11 +118,11 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TryStripRegionSuffix(new ModelId("bedrock/claude-opus-5@eu-central-1"), out var inner);
|
var taken = HostNaming.TryStripRegionSuffix(new ModelId("bedrock/claude-opus-5@eu-central-1"), out var inner);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.True);
|
Assert.That(taken, Is.True);
|
||||||
Assert.That(inner.Original, Is.EqualTo("bedrock/claude-opus-5"));
|
Assert.That(inner.Original, Is.EqualTo("bedrock/claude-opus-5"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("bedrock/claude-opus-5", TestName = "A name without an at sign has no region to take off")]
|
[TestCase("bedrock/claude-opus-5", TestName = "A name without an at sign has no region to take off")]
|
||||||
@@ -131,11 +131,11 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TryStripRegionSuffix(new ModelId(modelId), out var inner);
|
var taken = HostNaming.TryStripRegionSuffix(new ModelId(modelId), out var inner);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.False);
|
Assert.That(taken, Is.False);
|
||||||
Assert.That(inner.Original, Is.EqualTo(modelId));
|
Assert.That(inner.Original, Is.EqualTo(modelId));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -143,11 +143,11 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TryStripMenuPosition(new ModelId("10 - Muse Glimmer 30b - the newest META model"), out var inner);
|
var taken = HostNaming.TryStripMenuPosition(new ModelId("10 - Muse Glimmer 30b - the newest META model"), out var inner);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.True);
|
Assert.That(taken, Is.True);
|
||||||
Assert.That(inner.Original, Is.EqualTo("Muse Glimmer 30b - the newest META model"));
|
Assert.That(inner.Original, Is.EqualTo("Muse Glimmer 30b - the newest META model"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("70b-instruct", TestName = "A number the model is named after is not a menu position")]
|
[TestCase("70b-instruct", TestName = "A number the model is named after is not a menu position")]
|
||||||
@@ -158,10 +158,10 @@ public sealed class HostNamingTests
|
|||||||
{
|
{
|
||||||
var taken = HostNaming.TryStripMenuPosition(new ModelId(modelId), out var inner);
|
var taken = HostNaming.TryStripMenuPosition(new ModelId(modelId), out var inner);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(taken, Is.False);
|
Assert.That(taken, Is.False);
|
||||||
Assert.That(inner.Original, Is.EqualTo(modelId));
|
Assert.That(inner.Original, Is.EqualTo(modelId));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,12 +49,12 @@ public sealed class ModelHostIndexTests
|
|||||||
{
|
{
|
||||||
var index = ModelHostIndex.Build([new SplittingHost()]);
|
var index = ModelHostIndex.Build([new SplittingHost()]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(index.ProvidersWithoutAHost, Does.Contain(LLMProviders.ANTHROPIC));
|
Assert.That(index.ProvidersWithoutAHost, Does.Contain(LLMProviders.ANTHROPIC));
|
||||||
Assert.That(index.ProvidersWithoutAHost, Does.Not.Contain(LLMProviders.OPEN_ROUTER));
|
Assert.That(index.ProvidersWithoutAHost, Does.Not.Contain(LLMProviders.OPEN_ROUTER));
|
||||||
Assert.That(index.ProvidersWithoutAHost, Does.Not.Contain(LLMProviders.NONE), "Nobody can configure it, so nobody has to write a host for it.");
|
Assert.That(index.ProvidersWithoutAHost, Does.Not.Contain(LLMProviders.NONE), "Nobody can configure it, so nobody has to write a host for it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -63,12 +63,12 @@ public sealed class ModelHostIndexTests
|
|||||||
var index = ModelHostIndex.Build([new SplittingHost()]);
|
var index = ModelHostIndex.Build([new SplittingHost()]);
|
||||||
var unwrapped = index.Unwrap(new ModelId("anthropic/claude-opus-5"), LLMProviders.ANTHROPIC, out var vendor);
|
var unwrapped = index.Unwrap(new ModelId("anthropic/claude-opus-5"), LLMProviders.ANTHROPIC, out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(index.Of(LLMProviders.ANTHROPIC), Is.Null);
|
Assert.That(index.Of(LLMProviders.ANTHROPIC), Is.Null);
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("anthropic/claude-opus-5"));
|
Assert.That(unwrapped.Original, Is.EqualTo("anthropic/claude-opus-5"));
|
||||||
Assert.That(vendor, Is.Null);
|
Assert.That(vendor, Is.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -82,11 +82,11 @@ public sealed class ModelHostIndexTests
|
|||||||
var profile = new ModelProfile { Capabilities = Capability.TEXT_INPUT | Capability.RESPONSES_API };
|
var profile = new ModelProfile { Capabilities = Capability.TEXT_INPUT | Capability.RESPONSES_API };
|
||||||
var throughTheProvider = index.ApplyTransport(profile, LLMProviders.ANTHROPIC);
|
var throughTheProvider = index.ApplyTransport(profile, LLMProviders.ANTHROPIC);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(throughTheProvider.Has(Capability.RESPONSES_API), Is.False);
|
Assert.That(throughTheProvider.Has(Capability.RESPONSES_API), Is.False);
|
||||||
Assert.That(throughTheProvider.Has(Capability.CHAT_COMPLETION_API), Is.True);
|
Assert.That(throughTheProvider.Has(Capability.CHAT_COMPLETION_API), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.OPEN_ROUTER, "anthropic/claude-opus-5", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.OPEN_ROUTER, "anthropic/claude-opus-5", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5"));
|
Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5"));
|
||||||
Assert.That(vendor, Is.EqualTo(ModelVendor.ANTHROPIC));
|
Assert.That(vendor, Is.EqualTo(ModelVendor.ANTHROPIC));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -59,11 +59,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.HUGGINGFACE, "openai/gpt-oss-120b:fireworks-ai", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.HUGGINGFACE, "openai/gpt-oss-120b:fireworks-ai", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("gpt-oss-120b"));
|
Assert.That(unwrapped.Original, Is.EqualTo("gpt-oss-120b"));
|
||||||
Assert.That(vendor, Is.EqualTo(ModelVendor.OPEN_AI), "OpenAI published the weights, whoever is serving them today.");
|
Assert.That(vendor, Is.EqualTo(ModelVendor.OPEN_AI), "OpenAI published the weights, whoever is serving them today.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -71,11 +71,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.HUGGINGFACE, "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.HUGGINGFACE, "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("DeepSeek-R1-Distill-Qwen-32B"));
|
Assert.That(unwrapped.Original, Is.EqualTo("DeepSeek-R1-Distill-Qwen-32B"));
|
||||||
Assert.That(vendor, Is.EqualTo(ModelVendor.DEEP_SEEK));
|
Assert.That(vendor, Is.EqualTo(ModelVendor.DEEP_SEEK));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -83,11 +83,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.REQUESTY, "bedrock/claude-opus-5@eu-central-1", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.REQUESTY, "bedrock/claude-opus-5@eu-central-1", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5"));
|
Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5"));
|
||||||
Assert.That(vendor, Is.Null, "Bedrock runs the model; it did not build it.");
|
Assert.That(vendor, Is.Null, "Bedrock runs the model; it did not build it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -95,11 +95,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.REQUESTY, "claude-opus-5@eu", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.REQUESTY, "claude-opus-5@eu", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5"));
|
Assert.That(unwrapped.Original, Is.EqualTo("claude-opus-5"));
|
||||||
Assert.That(vendor, Is.Null);
|
Assert.That(vendor, Is.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -107,11 +107,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.REQUESTY, "openai/gpt-6-sol:flex", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.REQUESTY, "openai/gpt-6-sol:flex", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("gpt-6-sol"));
|
Assert.That(unwrapped.Original, Is.EqualTo("gpt-6-sol"));
|
||||||
Assert.That(vendor, Is.EqualTo(ModelVendor.OPEN_AI));
|
Assert.That(vendor, Is.EqualTo(ModelVendor.OPEN_AI));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("novita/qwen/qwen3.8-max", "qwen3.8-max", ModelVendor.ALIBABA, TestName = "Behind the cloud, the organization from the hub names the vendor")]
|
[TestCase("novita/qwen/qwen3.8-max", "qwen3.8-max", ModelVendor.ALIBABA, TestName = "Behind the cloud, the organization from the hub names the vendor")]
|
||||||
@@ -120,11 +120,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.REQUESTY, modelId, out var vendor);
|
var unwrapped = Unwrap(LLMProviders.REQUESTY, modelId, out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo(expectedModel));
|
Assert.That(unwrapped.Original, Is.EqualTo(expectedModel));
|
||||||
Assert.That(vendor, Is.EqualTo(expectedVendor));
|
Assert.That(vendor, Is.EqualTo(expectedVendor));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -132,11 +132,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.FIREWORKS, "accounts/fireworks/models/llama-v3p1-405b-instruct", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.FIREWORKS, "accounts/fireworks/models/llama-v3p1-405b-instruct", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("llama-v3p1-405b-instruct"));
|
Assert.That(unwrapped.Original, Is.EqualTo("llama-v3p1-405b-instruct"));
|
||||||
Assert.That(vendor, Is.Null, "None of the three path segments names a vendor.");
|
Assert.That(vendor, Is.Null, "None of the three path segments names a vendor.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -152,11 +152,11 @@ public sealed class ModelHostTests
|
|||||||
{
|
{
|
||||||
var unwrapped = Unwrap(LLMProviders.SELF_HOSTED, "meta-llama/Llama-3.3-70B-Instruct", out var vendor);
|
var unwrapped = Unwrap(LLMProviders.SELF_HOSTED, "meta-llama/Llama-3.3-70B-Instruct", out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo("Llama-3.3-70B-Instruct"));
|
Assert.That(unwrapped.Original, Is.EqualTo("Llama-3.3-70B-Instruct"));
|
||||||
Assert.That(vendor, Is.EqualTo(ModelVendor.META));
|
Assert.That(vendor, Is.EqualTo(ModelVendor.META));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -184,11 +184,11 @@ public sealed class ModelHostTests
|
|||||||
//
|
//
|
||||||
var unwrapped = Unwrap(LLMProviders.SELF_HOSTED, modelId, out var vendor);
|
var unwrapped = Unwrap(LLMProviders.SELF_HOSTED, modelId, out var vendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(unwrapped.Original, Is.EqualTo(expectedModel));
|
Assert.That(unwrapped.Original, Is.EqualTo(expectedModel));
|
||||||
Assert.That(vendor, Is.EqualTo(expectedVendor));
|
Assert.That(vendor, Is.EqualTo(expectedVendor));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -203,13 +203,13 @@ public sealed class ModelHostTests
|
|||||||
var atItsVendor = new ModelProfile { Capabilities = Capability.TEXT_INPUT | Capability.FUNCTION_CALLING | Capability.RESPONSES_API };
|
var atItsVendor = new ModelProfile { Capabilities = Capability.TEXT_INPUT | Capability.FUNCTION_CALLING | Capability.RESPONSES_API };
|
||||||
var throughTheReseller = Transport(LLMProviders.GWDG, atItsVendor);
|
var throughTheReseller = Transport(LLMProviders.GWDG, atItsVendor);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(Unwrap(LLMProviders.GWDG, "claude-sonnet-5", out _).Original, Is.EqualTo("claude-sonnet-5"));
|
Assert.That(Unwrap(LLMProviders.GWDG, "claude-sonnet-5", out _).Original, Is.EqualTo("claude-sonnet-5"));
|
||||||
Assert.That(throughTheReseller.Has(Capability.FUNCTION_CALLING), Is.True);
|
Assert.That(throughTheReseller.Has(Capability.FUNCTION_CALLING), Is.True);
|
||||||
Assert.That(throughTheReseller.Has(Capability.RESPONSES_API), Is.False);
|
Assert.That(throughTheReseller.Has(Capability.RESPONSES_API), Is.False);
|
||||||
Assert.That(throughTheReseller.Has(Capability.CHAT_COMPLETION_API), Is.True);
|
Assert.That(throughTheReseller.Has(Capability.CHAT_COMPLETION_API), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -221,11 +221,11 @@ public sealed class ModelHostTests
|
|||||||
.Where(host => host.ApplyTransport(withBothApis).Has(Capability.RESPONSES_API))
|
.Where(host => host.ApplyTransport(withBothApis).Has(Capability.RESPONSES_API))
|
||||||
.Select(host => host.GetType().Name);
|
.Select(host => host.GetType().Name);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(Transport(LLMProviders.OPEN_AI, withBothApis).Has(Capability.RESPONSES_API), Is.True);
|
Assert.That(Transport(LLMProviders.OPEN_AI, withBothApis).Has(Capability.RESPONSES_API), Is.True);
|
||||||
Assert.That(elsewhere, Is.Empty, "The app sends a Responses API request from exactly one place.");
|
Assert.That(elsewhere, Is.Empty, "The app sends a Responses API request from exactly one place.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -238,22 +238,22 @@ public sealed class ModelHostTests
|
|||||||
var embedding = new ModelProfile { Capabilities = Capability.EMBEDDING };
|
var embedding = new ModelProfile { Capabilities = Capability.EMBEDDING };
|
||||||
var throughAGateway = Transport(LLMProviders.OPEN_ROUTER, embedding);
|
var throughAGateway = Transport(LLMProviders.OPEN_ROUTER, embedding);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(throughAGateway.Has(Capability.EMBEDDING), Is.True);
|
Assert.That(throughAGateway.Has(Capability.EMBEDDING), Is.True);
|
||||||
Assert.That(throughAGateway.HasAny(Capability.CHAT_COMPLETION_API | Capability.RESPONSES_API), Is.False);
|
Assert.That(throughAGateway.HasAny(Capability.CHAT_COMPLETION_API | Capability.RESPONSES_API), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void ANameWithoutAWrappingComesBackAsItWas()
|
public void ANameWithoutAWrappingComesBackAsItWas()
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(Unwrap(LLMProviders.OPEN_AI, "gpt-5.6", out _).Original, Is.EqualTo("gpt-5.6"));
|
Assert.That(Unwrap(LLMProviders.OPEN_AI, "gpt-5.6", out _).Original, Is.EqualTo("gpt-5.6"));
|
||||||
Assert.That(Unwrap(LLMProviders.GROQ, "llama-3.3-70b-versatile", out _).Original, Is.EqualTo("llama-3.3-70b-versatile"));
|
Assert.That(Unwrap(LLMProviders.GROQ, "llama-3.3-70b-versatile", out _).Original, Is.EqualTo("llama-3.3-70b-versatile"));
|
||||||
Assert.That(Unwrap(LLMProviders.LITE_LLM, "the-fast-one", out _).Original, Is.EqualTo("the-fast-one"));
|
Assert.That(Unwrap(LLMProviders.LITE_LLM, "the-fast-one", out _).Original, Is.EqualTo("the-fast-one"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelId Unwrap(LLMProviders provider, string modelId, out ModelVendor? declaredVendor) => INDEX.Unwrap(new ModelId(modelId), provider, out declaredVendor);
|
private static ModelId Unwrap(LLMProviders provider, string modelId, out ModelVendor? declaredVendor) => INDEX.Unwrap(new ModelId(modelId), provider, out declaredVendor);
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ public sealed class ImageLimitRuleTests
|
|||||||
{
|
{
|
||||||
var limits = provider.GetModelProfile(new Model(modelId, null)).Images;
|
var limits = provider.GetModelProfile(new Model(modelId, null)).Images;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(limits.IsKnown, Is.True);
|
Assert.That(limits.IsKnown, Is.True);
|
||||||
Assert.That(limits.MaxPerRequest, Is.EqualTo(perRequest));
|
Assert.That(limits.MaxPerRequest, Is.EqualTo(perRequest));
|
||||||
Assert.That(limits.MaxPerMessage, Is.Null, "Neither vendor states a per-message limit, and inventing one would be a ceiling nobody wrote.");
|
Assert.That(limits.MaxPerMessage, Is.Null, "Neither vendor states a per-message limit, and inventing one would be a ceiling nobody wrote.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -57,13 +57,13 @@ public sealed class ImageLimitRuleTests
|
|||||||
var smallWindow = ModelRegistry.Shared.Profile(LLMProviders.ANTHROPIC, "claude-opus-4-1");
|
var smallWindow = ModelRegistry.Shared.Profile(LLMProviders.ANTHROPIC, "claude-opus-4-1");
|
||||||
var largeWindow = ModelRegistry.Shared.Profile(LLMProviders.ANTHROPIC, "claude-opus-4-6");
|
var largeWindow = ModelRegistry.Shared.Profile(LLMProviders.ANTHROPIC, "claude-opus-4-6");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(smallWindow.Context.DefaultTokens, Is.EqualTo(200_000));
|
Assert.That(smallWindow.Context.DefaultTokens, Is.EqualTo(200_000));
|
||||||
Assert.That(smallWindow.Images.MaxPerRequest, Is.EqualTo(100));
|
Assert.That(smallWindow.Images.MaxPerRequest, Is.EqualTo(100));
|
||||||
Assert.That(largeWindow.Context.DefaultTokens, Is.EqualTo(1_000_000));
|
Assert.That(largeWindow.Context.DefaultTokens, Is.EqualTo(1_000_000));
|
||||||
Assert.That(largeWindow.Images.MaxPerRequest, Is.EqualTo(600));
|
Assert.That(largeWindow.Images.MaxPerRequest, Is.EqualTo(600));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -76,11 +76,11 @@ public sealed class ImageLimitRuleTests
|
|||||||
//
|
//
|
||||||
var profile = ModelRegistry.Shared.Profile(LLMProviders.SELF_HOSTED, "some-model-nobody-wrote-a-rule-for");
|
var profile = ModelRegistry.Shared.Profile(LLMProviders.SELF_HOSTED, "some-model-nobody-wrote-a-rule-for");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Images.IsKnown, Is.False);
|
Assert.That(profile.Images.IsKnown, Is.False);
|
||||||
Assert.That(profile.Images.MaxInOneMessage, Is.Null);
|
Assert.That(profile.Images.MaxInOneMessage, Is.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -93,11 +93,11 @@ public sealed class ImageLimitRuleTests
|
|||||||
//
|
//
|
||||||
var profile = ModelRegistry.Shared.Profile(LLMProviders.OPEN_AI, "gpt-5.1");
|
var profile = ModelRegistry.Shared.Profile(LLMProviders.OPEN_AI, "gpt-5.1");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True, "It does read several images.");
|
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True, "It does read several images.");
|
||||||
Assert.That(profile.Images.IsKnown, Is.False, "How many, nobody said.");
|
Assert.That(profile.Images.IsKnown, Is.False, "How many, nobody said.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ public sealed class ListedModelsTests
|
|||||||
{
|
{
|
||||||
var listed = new ListedModels();
|
var listed = new ListedModels();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING));
|
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING));
|
||||||
Assert.That(listed.Of(ONE_MACHINE, MODEL).IsKnown, Is.False);
|
Assert.That(listed.Of(ONE_MACHINE, MODEL).IsKnown, Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -53,11 +53,11 @@ public sealed class ListedModelsTests
|
|||||||
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(32_768))]);
|
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(32_768))]);
|
||||||
listed.Report(ANOTHER_MACHINE, [new(MODEL, ContextWindow.Of(8_192))]);
|
listed.Report(ANOTHER_MACHINE, [new(MODEL, ContextWindow.Of(8_192))]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(listed.Of(ONE_MACHINE, MODEL).Context.DefaultTokens, Is.EqualTo(32_768));
|
Assert.That(listed.Of(ONE_MACHINE, MODEL).Context.DefaultTokens, Is.EqualTo(32_768));
|
||||||
Assert.That(listed.Of(ANOTHER_MACHINE, MODEL).Context.DefaultTokens, Is.EqualTo(8_192));
|
Assert.That(listed.Of(ANOTHER_MACHINE, MODEL).Context.DefaultTokens, Is.EqualTo(8_192));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -67,11 +67,11 @@ public sealed class ListedModelsTests
|
|||||||
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(32_768)), new("gemma3-27b", ContextWindow.Of(16_384))]);
|
listed.Report(ONE_MACHINE, [new(MODEL, ContextWindow.Of(32_768)), new("gemma3-27b", ContextWindow.Of(16_384))]);
|
||||||
listed.Report(ONE_MACHINE, [new("gemma3-27b", ContextWindow.Of(16_384))]);
|
listed.Report(ONE_MACHINE, [new("gemma3-27b", ContextWindow.Of(16_384))]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING), "The engine was restarted without it, so nothing is known about it any more.");
|
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING), "The engine was restarted without it, so nothing is known about it any more.");
|
||||||
Assert.That(listed.Of(ONE_MACHINE, "gemma3-27b").Context.DefaultTokens, Is.EqualTo(16_384));
|
Assert.That(listed.Of(ONE_MACHINE, "gemma3-27b").Context.DefaultTokens, Is.EqualTo(16_384));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -105,11 +105,11 @@ public sealed class ListedModelsTests
|
|||||||
var listed = new ListedModels();
|
var listed = new ListedModels();
|
||||||
listed.Report(string.Empty, [new(MODEL, ContextWindow.Of(32_768))]);
|
listed.Report(string.Empty, [new(MODEL, ContextWindow.Of(32_768))]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(listed.Of(string.Empty, MODEL), Is.EqualTo(ModelListing.NOTHING));
|
Assert.That(listed.Of(string.Empty, MODEL), Is.EqualTo(ModelListing.NOTHING));
|
||||||
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING), "One nameless report does not become every machine's answer.");
|
Assert.That(listed.Of(ONE_MACHINE, MODEL), Is.EqualTo(ModelListing.NOTHING), "One nameless report does not become every machine's answer.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -126,11 +126,11 @@ public sealed class ListedModelsTests
|
|||||||
{
|
{
|
||||||
var after = new ModelListing(MODEL, ContextWindow.Of(32_768)).ApplyTo(WHAT_THE_RULES_SAY);
|
var after = new ModelListing(MODEL, ContextWindow.Of(32_768)).ApplyTo(WHAT_THE_RULES_SAY);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(after.Context.DefaultTokens, Is.EqualTo(32_768));
|
Assert.That(after.Context.DefaultTokens, Is.EqualTo(32_768));
|
||||||
Assert.That(after.Context.RaisableToTokens, Is.Null, "What the weights could be raised to is not a number anybody reaches without restarting this engine.");
|
Assert.That(after.Context.RaisableToTokens, Is.Null, "What the weights could be raised to is not a number anybody reaches without restarting this engine.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -138,12 +138,12 @@ public sealed class ListedModelsTests
|
|||||||
{
|
{
|
||||||
var after = new ModelListing(MODEL, ContextWindow.Of(32_768)).ApplyTo(WHAT_THE_RULES_SAY);
|
var after = new ModelListing(MODEL, ContextWindow.Of(32_768)).ApplyTo(WHAT_THE_RULES_SAY);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(after.Capabilities, Is.EqualTo(WHAT_THE_RULES_SAY.Capabilities));
|
Assert.That(after.Capabilities, Is.EqualTo(WHAT_THE_RULES_SAY.Capabilities));
|
||||||
Assert.That(after.Images, Is.EqualTo(WHAT_THE_RULES_SAY.Images));
|
Assert.That(after.Images, Is.EqualTo(WHAT_THE_RULES_SAY.Images));
|
||||||
Assert.That(after.Kind, Is.EqualTo(WHAT_THE_RULES_SAY.Kind));
|
Assert.That(after.Kind, Is.EqualTo(WHAT_THE_RULES_SAY.Kind));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ public sealed class MatchPatternTests
|
|||||||
var onAlibaba = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "qwq", OnlyOn = LLMProviders.ALIBABA_CLOUD };
|
var onAlibaba = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "qwq", OnlyOn = LLMProviders.ALIBABA_CLOUD };
|
||||||
var name = new ModelId("qwq-32b");
|
var name = new ModelId("qwq-32b");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(onAlibaba.Matches(name, LLMProviders.ALIBABA_CLOUD, ModelVendor.UNKNOWN), Is.True);
|
Assert.That(onAlibaba.Matches(name, LLMProviders.ALIBABA_CLOUD, ModelVendor.UNKNOWN), Is.True);
|
||||||
Assert.That(onAlibaba.Matches(name, LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False);
|
Assert.That(onAlibaba.Matches(name, LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -34,11 +34,11 @@ public sealed class MatchPatternTests
|
|||||||
var fromAnthropic = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "claude", OnlyFrom = ModelVendor.ANTHROPIC };
|
var fromAnthropic = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "claude", OnlyFrom = ModelVendor.ANTHROPIC };
|
||||||
var name = new ModelId("claude-sonnet-4-0");
|
var name = new ModelId("claude-sonnet-4-0");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(fromAnthropic.Matches(name, LLMProviders.LITE_LLM, ModelVendor.ANTHROPIC), Is.True);
|
Assert.That(fromAnthropic.Matches(name, LLMProviders.LITE_LLM, ModelVendor.ANTHROPIC), Is.True);
|
||||||
Assert.That(fromAnthropic.Matches(name, LLMProviders.LITE_LLM, ModelVendor.UNKNOWN), Is.False);
|
Assert.That(fromAnthropic.Matches(name, LLMProviders.LITE_LLM, ModelVendor.UNKNOWN), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -46,12 +46,12 @@ public sealed class MatchPatternTests
|
|||||||
{
|
{
|
||||||
var withVision = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "qwen3.8", AlsoContains = ["vl"] };
|
var withVision = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "qwen3.8", AlsoContains = ["vl"] };
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(withVision.Matches(new ModelId("qwen3.8-27b-vl"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.True);
|
Assert.That(withVision.Matches(new ModelId("qwen3.8-27b-vl"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.True);
|
||||||
Assert.That(withVision.Matches(new ModelId("qwen3.8-27b"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False);
|
Assert.That(withVision.Matches(new ModelId("qwen3.8-27b"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False);
|
||||||
Assert.That(withVision.Matches(new ModelId("qwen3.8-27b-vllm"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False, "\"vl\" inside another name part is not the vision variant.");
|
Assert.That(withVision.Matches(new ModelId("qwen3.8-27b-vllm"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False, "\"vl\" inside another name part is not the vision variant.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -62,11 +62,11 @@ public sealed class MatchPatternTests
|
|||||||
//
|
//
|
||||||
var withoutTools = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "salamandra", NotContains = ["tools"] };
|
var withoutTools = new MatchPattern { Kind = MatchKind.SEGMENT, Text = "salamandra", NotContains = ["tools"] };
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(withoutTools.Matches(new ModelId("salamandra-7b-instruct"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.True);
|
Assert.That(withoutTools.Matches(new ModelId("salamandra-7b-instruct"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.True);
|
||||||
Assert.That(withoutTools.Matches(new ModelId("salamandra-7b-instruct-tools"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False);
|
Assert.That(withoutTools.Matches(new ModelId("salamandra-7b-instruct-tools"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("gpt-5.1", true)]
|
[TestCase("gpt-5.1", true)]
|
||||||
|
|||||||
@@ -29,11 +29,11 @@ public sealed class ModelFamilyIndexTests
|
|||||||
var distill = Selector("deepseek-r1", MatchKind.SEGMENT, new() { Adds = Capability.TEXT_INPUT | Capability.FUNCTION_CALLING, Reasoning = ReasoningSupport.ALWAYS });
|
var distill = Selector("deepseek-r1", MatchKind.SEGMENT, new() { Adds = Capability.TEXT_INPUT | Capability.FUNCTION_CALLING, Reasoning = ReasoningSupport.ALWAYS });
|
||||||
var name = new ModelId("deepseek-r1-distill-llama-70b");
|
var name = new ModelId("deepseek-r1-distill-llama-70b");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(ModelFamilyIndex.Build([llama, distill]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN).Selector, Is.SameAs(distill));
|
Assert.That(ModelFamilyIndex.Build([llama, distill]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN).Selector, Is.SameAs(distill));
|
||||||
Assert.That(ModelFamilyIndex.Build([distill, llama]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN).Selector, Is.SameAs(distill), "The order the rules arrive in must not change the answer.");
|
Assert.That(ModelFamilyIndex.Build([distill, llama]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN).Selector, Is.SameAs(distill), "The order the rules arrive in must not change the answer.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -47,11 +47,11 @@ public sealed class ModelFamilyIndexTests
|
|||||||
var chat = Selector("gpt-5-chat", MatchKind.PREFIX, new() { Adds = Capability.TEXT_INPUT, Reasoning = ReasoningSupport.NONE });
|
var chat = Selector("gpt-5-chat", MatchKind.PREFIX, new() { Adds = Capability.TEXT_INPUT, Reasoning = ReasoningSupport.NONE });
|
||||||
var index = ModelFamilyIndex.Build([reasoning, chat]);
|
var index = ModelFamilyIndex.Build([reasoning, chat]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(index.Resolve(new ModelId("gpt-5-chat-latest"), ANY_PROVIDER, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(index.Resolve(new ModelId("gpt-5-chat-latest"), ANY_PROVIDER, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
Assert.That(index.Resolve(new ModelId("gpt-5-pro"), ANY_PROVIDER, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.ALWAYS));
|
Assert.That(index.Resolve(new ModelId("gpt-5-pro"), ANY_PROVIDER, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.ALWAYS));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -79,11 +79,11 @@ public sealed class ModelFamilyIndexTests
|
|||||||
var index = ModelFamilyIndex.Build([openWeights, commercial]);
|
var index = ModelFamilyIndex.Build([openWeights, commercial]);
|
||||||
var name = new ModelId("qwq-32b");
|
var name = new ModelId("qwq-32b");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(index.Explain(name, LLMProviders.ALIBABA_CLOUD, ModelVendor.UNKNOWN).Selector, Is.SameAs(commercial));
|
Assert.That(index.Explain(name, LLMProviders.ALIBABA_CLOUD, ModelVendor.UNKNOWN).Selector, Is.SameAs(commercial));
|
||||||
Assert.That(index.Explain(name, LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN).Selector, Is.SameAs(openWeights));
|
Assert.That(index.Explain(name, LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN).Selector, Is.SameAs(openWeights));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -98,11 +98,11 @@ public sealed class ModelFamilyIndexTests
|
|||||||
var baseCheckpoint = Modifier("base", MatchKind.SEGMENT, new() { Removes = Capability.FUNCTION_CALLING });
|
var baseCheckpoint = Modifier("base", MatchKind.SEGMENT, new() { Removes = Capability.FUNCTION_CALLING });
|
||||||
var index = ModelFamilyIndex.Build([family, baseCheckpoint]);
|
var index = ModelFamilyIndex.Build([family, baseCheckpoint]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(index.Resolve(new ModelId("llama-3.3-70b"), ANY_PROVIDER, ModelVendor.UNKNOWN).Has(Capability.FUNCTION_CALLING), Is.True);
|
Assert.That(index.Resolve(new ModelId("llama-3.3-70b"), ANY_PROVIDER, ModelVendor.UNKNOWN).Has(Capability.FUNCTION_CALLING), Is.True);
|
||||||
Assert.That(index.Resolve(new ModelId("llama-3.3-70b-base"), ANY_PROVIDER, ModelVendor.UNKNOWN).Has(Capability.FUNCTION_CALLING), Is.False);
|
Assert.That(index.Resolve(new ModelId("llama-3.3-70b-base"), ANY_PROVIDER, ModelVendor.UNKNOWN).Has(Capability.FUNCTION_CALLING), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -113,11 +113,11 @@ public sealed class ModelFamilyIndexTests
|
|||||||
var narrow = Modifier("instruct-nano", MatchKind.SEGMENT, new() { Removes = Capability.FUNCTION_CALLING });
|
var narrow = Modifier("instruct-nano", MatchKind.SEGMENT, new() { Removes = Capability.FUNCTION_CALLING });
|
||||||
var resolution = ModelFamilyIndex.Build([narrow, broad, family]).Explain(new ModelId("llama-3.3-instruct-nano"), ANY_PROVIDER, ModelVendor.UNKNOWN);
|
var resolution = ModelFamilyIndex.Build([narrow, broad, family]).Explain(new ModelId("llama-3.3-instruct-nano"), ANY_PROVIDER, ModelVendor.UNKNOWN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(resolution.Modifiers.Select(modifier => modifier.Pattern.Text), Is.EqualTo(new[] { "instruct", "instruct-nano" }));
|
Assert.That(resolution.Modifiers.Select(modifier => modifier.Pattern.Text), Is.EqualTo(new[] { "instruct", "instruct-nano" }));
|
||||||
Assert.That(resolution.Profile.Has(Capability.FUNCTION_CALLING), Is.False);
|
Assert.That(resolution.Profile.Has(Capability.FUNCTION_CALLING), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -154,12 +154,12 @@ public sealed class ModelFamilyIndexTests
|
|||||||
var oneWay = ModelFamilyIndex.Build([one, other]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN);
|
var oneWay = ModelFamilyIndex.Build([one, other]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN);
|
||||||
var otherWay = ModelFamilyIndex.Build([other, one]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN);
|
var otherWay = ModelFamilyIndex.Build([other, one]).Explain(name, ANY_PROVIDER, ModelVendor.UNKNOWN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(oneWay.IsAmbiguous, Is.True);
|
Assert.That(oneWay.IsAmbiguous, Is.True);
|
||||||
Assert.That(otherWay.IsAmbiguous, Is.True);
|
Assert.That(otherWay.IsAmbiguous, Is.True);
|
||||||
Assert.That(oneWay.Selector, Is.SameAs(otherWay.Selector), "Which of the two answers must not depend on the order they arrived in.");
|
Assert.That(oneWay.Selector, Is.SameAs(otherWay.Selector), "Which of the two answers must not depend on the order they arrived in.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -190,11 +190,11 @@ public sealed class ModelFamilyIndexTests
|
|||||||
var index = ModelFamilyIndex.Build([Selector("llama", MatchKind.SEGMENT, new() { Adds = Capability.TEXT_INPUT })]);
|
var index = ModelFamilyIndex.Build([Selector("llama", MatchKind.SEGMENT, new() { Adds = Capability.TEXT_INPUT })]);
|
||||||
var resolution = index.Explain(new ModelId("something-nobody-wrote-a-rule-for"), ANY_PROVIDER, ModelVendor.UNKNOWN);
|
var resolution = index.Explain(new ModelId("something-nobody-wrote-a-rule-for"), ANY_PROVIDER, ModelVendor.UNKNOWN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(resolution.IsKnown, Is.False);
|
Assert.That(resolution.IsKnown, Is.False);
|
||||||
Assert.That(resolution.Profile, Is.EqualTo(ModelProfile.UNKNOWN));
|
Assert.That(resolution.Profile, Is.EqualTo(ModelProfile.UNKNOWN));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -227,11 +227,11 @@ public sealed class ModelFamilyIndexTests
|
|||||||
//
|
//
|
||||||
var index = ModelFamilyIndex.Build([Selector("GPT-5", MatchKind.PREFIX, new() { Adds = Capability.TEXT_INPUT })]);
|
var index = ModelFamilyIndex.Build([Selector("GPT-5", MatchKind.PREFIX, new() { Adds = Capability.TEXT_INPUT })]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(index.Rules.Where(rule => !rule.Pattern.IsWellFormed), Is.Not.Empty);
|
Assert.That(index.Rules.Where(rule => !rule.Pattern.IsWellFormed), Is.Not.Empty);
|
||||||
Assert.That(index.Explain(new ModelId("gpt-5.1"), ANY_PROVIDER, ModelVendor.UNKNOWN).IsKnown, Is.False);
|
Assert.That(index.Explain(new ModelId("gpt-5.1"), ANY_PROVIDER, ModelVendor.UNKNOWN).IsKnown, Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ModelRule Selector(string text, MatchKind kind, ModelProfileChange change, string origin = "test") => new(new() { Kind = kind, Text = text }, ModelRuleKind.SELECTOR, change, origin);
|
private static ModelRule Selector(string text, MatchKind kind, ModelProfileChange change, string origin = "test") => new(new() { Kind = kind, Text = text }, ModelRuleKind.SELECTOR, change, origin);
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ public sealed class ModelIdTests
|
|||||||
{
|
{
|
||||||
var id = new ModelId(reported);
|
var id = new ModelId(reported);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(id.IsEmpty, Is.True);
|
Assert.That(id.IsEmpty, Is.True);
|
||||||
Assert.That(id.Normalized, Is.Empty);
|
Assert.That(id.Normalized, Is.Empty);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -42,11 +42,11 @@ public sealed class ModelIdTests
|
|||||||
{
|
{
|
||||||
var id = new ModelId("Qwen3.8:27B-MLX");
|
var id = new ModelId("Qwen3.8:27B-MLX");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(id.Original, Is.EqualTo("Qwen3.8:27B-MLX"));
|
Assert.That(id.Original, Is.EqualTo("Qwen3.8:27B-MLX"));
|
||||||
Assert.That(id.ToString(), Is.EqualTo("Qwen3.8:27B-MLX"));
|
Assert.That(id.ToString(), Is.EqualTo("Qwen3.8:27B-MLX"));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -54,13 +54,13 @@ public sealed class ModelIdTests
|
|||||||
{
|
{
|
||||||
ModelId untouched = default;
|
ModelId untouched = default;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(untouched.IsEmpty, Is.True);
|
Assert.That(untouched.IsEmpty, Is.True);
|
||||||
Assert.That(untouched.Original, Is.Empty);
|
Assert.That(untouched.Original, Is.Empty);
|
||||||
Assert.That(untouched.Normalized, Is.Empty);
|
Assert.That(untouched.Normalized, Is.Empty);
|
||||||
Assert.That(untouched.Segments.GetEnumerator().MoveNext(), Is.False);
|
Assert.That(untouched.Segments.GetEnumerator().MoveNext(), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -69,11 +69,11 @@ public sealed class ModelIdTests
|
|||||||
var fromOllama = new ModelId("Qwen3.8:27b");
|
var fromOllama = new ModelId("Qwen3.8:27b");
|
||||||
var fromHub = new ModelId("qwen3.8-27b");
|
var fromHub = new ModelId("qwen3.8-27b");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(fromOllama, Is.EqualTo(fromHub));
|
Assert.That(fromOllama, Is.EqualTo(fromHub));
|
||||||
Assert.That(fromOllama.GetHashCode(), Is.EqualTo(fromHub.GetHashCode()));
|
Assert.That(fromOllama.GetHashCode(), Is.EqualTo(fromHub.GetHashCode()));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -82,10 +82,10 @@ public sealed class RuleSpecificityTests
|
|||||||
var winner = RuleSpecificity.Of(expectedWinner);
|
var winner = RuleSpecificity.Of(expectedWinner);
|
||||||
var loser = RuleSpecificity.Of(expectedLoser);
|
var loser = RuleSpecificity.Of(expectedLoser);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(winner.CompareTo(loser), Is.GreaterThan(0));
|
Assert.That(winner.CompareTo(loser), Is.GreaterThan(0));
|
||||||
Assert.That(loser.CompareTo(winner), Is.LessThan(0), "The comparison has to say the same thing in both directions.");
|
Assert.That(loser.CompareTo(winner), Is.LessThan(0), "The comparison has to say the same thing in both directions.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,14 +53,14 @@ public sealed class MistralReleasesTests
|
|||||||
// for "mistral-medium-3" would otherwise answer for "mistral-medium-3.5" as well and place
|
// for "mistral-medium-3" would otherwise answer for "mistral-medium-3.5" as well and place
|
||||||
// it eleven months too early, before the release which gave it reasoning.
|
// it eleven months too early, before the release which gave it reasoning.
|
||||||
//
|
//
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(MistralReleases.Of(new ModelId("mistral-medium-3"), SOME_LATEST_RELEASE), Is.EqualTo(2505));
|
Assert.That(MistralReleases.Of(new ModelId("mistral-medium-3"), SOME_LATEST_RELEASE), Is.EqualTo(2505));
|
||||||
Assert.That(MistralReleases.Of(new ModelId("mistral-medium-3.5"), SOME_LATEST_RELEASE), Is.EqualTo(2604));
|
Assert.That(MistralReleases.Of(new ModelId("mistral-medium-3.5"), SOME_LATEST_RELEASE), Is.EqualTo(2604));
|
||||||
Assert.That(MistralReleases.Of(new ModelId("mistral-medium-3-5"), SOME_LATEST_RELEASE), Is.EqualTo(2604), "Mistral writes the version separator both ways for the same model.");
|
Assert.That(MistralReleases.Of(new ModelId("mistral-medium-3-5"), SOME_LATEST_RELEASE), Is.EqualTo(2604), "Mistral writes the version separator both ways for the same model.");
|
||||||
Assert.That(MistralReleases.Of(new ModelId("mistral-large-4"), SOME_LATEST_RELEASE), Is.EqualTo(2610));
|
Assert.That(MistralReleases.Of(new ModelId("mistral-large-4"), SOME_LATEST_RELEASE), Is.EqualTo(2610));
|
||||||
Assert.That(MistralReleases.Of(new ModelId("mistral-large-4-0"), SOME_LATEST_RELEASE), Is.EqualTo(2610), "The model card names it with a minor version, which changes nothing about the release.");
|
Assert.That(MistralReleases.Of(new ModelId("mistral-large-4-0"), SOME_LATEST_RELEASE), Is.EqualTo(2610), "The model card names it with a minor version, which changes nothing about the release.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -73,14 +73,14 @@ public sealed class MistralReleasesTests
|
|||||||
var beforeItCouldSee = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "mistral-large-2411");
|
var beforeItCouldSee = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "mistral-large-2411");
|
||||||
var afterwards = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "mistral-large-2512");
|
var afterwards = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "mistral-large-2512");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(beforeItCouldSee.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
Assert.That(beforeItCouldSee.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
||||||
Assert.That(beforeItCouldSee.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(beforeItCouldSee.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
|
|
||||||
Assert.That(afterwards.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
Assert.That(afterwards.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
||||||
Assert.That(afterwards.Reasoning, Is.EqualTo(ReasoningSupport.OPTIONAL));
|
Assert.That(afterwards.Reasoning, Is.EqualTo(ReasoningSupport.OPTIONAL));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("mistral-large-4", TestName = "Mistral Large 4 under the name of Mistral's changelog")]
|
[TestCase("mistral-large-4", TestName = "Mistral Large 4 under the name of Mistral's changelog")]
|
||||||
@@ -93,12 +93,12 @@ public sealed class MistralReleasesTests
|
|||||||
//
|
//
|
||||||
var profile = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, modelId);
|
var profile = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, modelId);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
||||||
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.OPTIONAL));
|
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.OPTIONAL));
|
||||||
Assert.That(profile.Context.DefaultTokens, Is.EqualTo(1_000_000));
|
Assert.That(profile.Context.DefaultTokens, Is.EqualTo(1_000_000));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("mistral-large-2512", TestName = "Mistral Large 3 keeps its own window")]
|
[TestCase("mistral-large-2512", TestName = "Mistral Large 3 keeps its own window")]
|
||||||
@@ -119,12 +119,12 @@ public sealed class MistralReleasesTests
|
|||||||
//
|
//
|
||||||
var profile = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "mistral-large-whenever");
|
var profile = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "mistral-large-whenever");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.True, "What the family could always do is still stated.");
|
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.True, "What the family could always do is still stated.");
|
||||||
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
||||||
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -132,10 +132,10 @@ public sealed class MistralReleasesTests
|
|||||||
{
|
{
|
||||||
var newest = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "ministral-3b-latest");
|
var newest = ModelRegistry.Shared.Profile(LLMProviders.MISTRAL, "ministral-3b-latest");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(newest.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True, "Ministral 3 reads images.");
|
Assert.That(newest.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True, "Ministral 3 reads images.");
|
||||||
Assert.That(newest.Reasoning, Is.EqualTo(ReasoningSupport.NONE), "No Ministral reasons, whatever its release.");
|
Assert.That(newest.Reasoning, Is.EqualTo(ReasoningSupport.NONE), "No Ministral reasons, whatever its release.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,11 +20,11 @@ public sealed class ModelFactsTests
|
|||||||
{
|
{
|
||||||
ContextWindow untouched = default;
|
ContextWindow untouched = default;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(untouched.IsKnown, Is.False);
|
Assert.That(untouched.IsKnown, Is.False);
|
||||||
Assert.That(untouched, Is.EqualTo(ContextWindow.UNKNOWN));
|
Assert.That(untouched, Is.EqualTo(ContextWindow.UNKNOWN));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -32,12 +32,12 @@ public sealed class ModelFactsTests
|
|||||||
{
|
{
|
||||||
var window = ContextWindow.Of(128_000, 1_000_000);
|
var window = ContextWindow.Of(128_000, 1_000_000);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(window.IsKnown, Is.True);
|
Assert.That(window.IsKnown, Is.True);
|
||||||
Assert.That(window.DefaultTokens, Is.EqualTo(128_000));
|
Assert.That(window.DefaultTokens, Is.EqualTo(128_000));
|
||||||
Assert.That(window.RaisableToTokens, Is.EqualTo(1_000_000));
|
Assert.That(window.RaisableToTokens, Is.EqualTo(1_000_000));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -59,11 +59,11 @@ public sealed class ModelFactsTests
|
|||||||
{
|
{
|
||||||
TokenizerRef untouched = default;
|
TokenizerRef untouched = default;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(untouched.IsKnown, Is.False);
|
Assert.That(untouched.IsKnown, Is.False);
|
||||||
Assert.That(untouched.Kind, Is.EqualTo(TokenizerKind.UNKNOWN));
|
Assert.That(untouched.Kind, Is.EqualTo(TokenizerKind.UNKNOWN));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -79,12 +79,12 @@ public sealed class ModelFactsTests
|
|||||||
{
|
{
|
||||||
ImageLimits untouched = default;
|
ImageLimits untouched = default;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(untouched.IsKnown, Is.False);
|
Assert.That(untouched.IsKnown, Is.False);
|
||||||
Assert.That(untouched.MaxPerMessage, Is.Null);
|
Assert.That(untouched.MaxPerMessage, Is.Null);
|
||||||
Assert.That(untouched.MaxPerRequest, Is.Null);
|
Assert.That(untouched.MaxPerRequest, Is.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -92,11 +92,11 @@ public sealed class ModelFactsTests
|
|||||||
{
|
{
|
||||||
var noImages = new ImageLimits(MaxPerMessage: 0, MaxPerRequest: null);
|
var noImages = new ImageLimits(MaxPerMessage: 0, MaxPerRequest: null);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(noImages.IsKnown, Is.True, "Zero images is a statement an operator can make.");
|
Assert.That(noImages.IsKnown, Is.True, "Zero images is a statement an operator can make.");
|
||||||
Assert.That(noImages.MaxPerMessage, Is.EqualTo(0));
|
Assert.That(noImages.MaxPerMessage, Is.EqualTo(0));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ public sealed class ModelFamilyTests
|
|||||||
{
|
{
|
||||||
var family = new SampleFamily();
|
var family = new SampleFamily();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(family.Name, Is.EqualTo(nameof(SampleFamily)));
|
Assert.That(family.Name, Is.EqualTo(nameof(SampleFamily)));
|
||||||
Assert.That(family.Rules.Select(rule => rule.Origin), Is.All.EqualTo(nameof(SampleFamily)));
|
Assert.That(family.Rules.Select(rule => rule.Origin), Is.All.EqualTo(nameof(SampleFamily)));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -52,7 +52,7 @@ public sealed class ModelFamilyTests
|
|||||||
var index = ModelFamilyIndex.Build(new SampleFamily().Rules);
|
var index = ModelFamilyIndex.Build(new SampleFamily().Rules);
|
||||||
var codex = index.Resolve(new ModelId("gpt-5.1-codex-max"), LLMProviders.OPEN_AI, ModelVendor.OPEN_AI);
|
var codex = index.Resolve(new ModelId("gpt-5.1-codex-max"), LLMProviders.OPEN_AI, ModelVendor.OPEN_AI);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(codex.Has(Capability.WEB_SEARCH), Is.False, "This is the one thing the variant takes away.");
|
Assert.That(codex.Has(Capability.WEB_SEARCH), Is.False, "This is the one thing the variant takes away.");
|
||||||
Assert.That(codex.Has(Capability.TEXT_INPUT | Capability.MULTIPLE_IMAGE_INPUT | Capability.FUNCTION_CALLING), Is.True);
|
Assert.That(codex.Has(Capability.TEXT_INPUT | Capability.MULTIPLE_IMAGE_INPUT | Capability.FUNCTION_CALLING), Is.True);
|
||||||
@@ -60,7 +60,7 @@ public sealed class ModelFamilyTests
|
|||||||
Assert.That(codex.Context.DefaultTokens, Is.EqualTo(400_000));
|
Assert.That(codex.Context.DefaultTokens, Is.EqualTo(400_000));
|
||||||
Assert.That(codex.Tokenizer.Id, Is.EqualTo("o200k_base"));
|
Assert.That(codex.Tokenizer.Id, Is.EqualTo("o200k_base"));
|
||||||
Assert.That(codex.SystemPromptRole, Is.EqualTo(SystemPromptRole.DEVELOPER));
|
Assert.That(codex.SystemPromptRole, Is.EqualTo(SystemPromptRole.DEVELOPER));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -69,11 +69,11 @@ public sealed class ModelFamilyTests
|
|||||||
var index = ModelFamilyIndex.Build(new SampleFamily().Rules.Concat(new FamilyWithAModifier().Rules));
|
var index = ModelFamilyIndex.Build(new SampleFamily().Rules.Concat(new FamilyWithAModifier().Rules));
|
||||||
var adjusted = index.Resolve(new ModelId("gpt-5.1-base"), LLMProviders.OPEN_AI, ModelVendor.OPEN_AI);
|
var adjusted = index.Resolve(new ModelId("gpt-5.1-base"), LLMProviders.OPEN_AI, ModelVendor.OPEN_AI);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(adjusted.Has(Capability.FUNCTION_CALLING), Is.False, "The modifier did apply.");
|
Assert.That(adjusted.Has(Capability.FUNCTION_CALLING), Is.False, "The modifier did apply.");
|
||||||
Assert.That(adjusted.SystemPromptRole, Is.EqualTo(SystemPromptRole.DEVELOPER));
|
Assert.That(adjusted.SystemPromptRole, Is.EqualTo(SystemPromptRole.DEVELOPER));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -99,11 +99,11 @@ public sealed class ModelFamilyTests
|
|||||||
{
|
{
|
||||||
var index = ModelFamilyIndex.Build(new FamilyWithTwoGenerations().Rules);
|
var index = ModelFamilyIndex.Build(new FamilyWithTwoGenerations().Rules);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(index.Resolve(new ModelId("thing3-mini"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.ALWAYS));
|
Assert.That(index.Resolve(new ModelId("thing3-mini"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.ALWAYS));
|
||||||
Assert.That(index.Resolve(new ModelId("thing4"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(index.Resolve(new ModelId("thing4"), LLMProviders.SELF_HOSTED, ModelVendor.UNKNOWN).Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -174,12 +174,12 @@ public sealed class ModelFamilyTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void ASourceWithoutAPageOrADayIsNotAStatement()
|
public void ASourceWithoutAPageOrADayIsNotAStatement()
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(new SampleFamily().Source.IsStated, Is.True);
|
Assert.That(new SampleFamily().Source.IsStated, Is.True);
|
||||||
Assert.That(new ModelSource(string.Empty, new DateOnly(2026, 9, 11), "a note").IsStated, Is.False);
|
Assert.That(new ModelSource(string.Empty, new DateOnly(2026, 9, 11), "a note").IsStated, Is.False);
|
||||||
Assert.That(new ModelSource("https://example.invalid", default, "a note").IsStated, Is.False);
|
Assert.That(new ModelSource("https://example.invalid", default, "a note").IsStated, Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public sealed class ModelKindTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void EveryExampleIsRecognizedAsWhatItIsMadeFor()
|
public void EveryExampleIsRecognizedAsWhatItIsMadeFor()
|
||||||
{
|
{
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
foreach (var example in ModelKindCorpus.ENTRIES)
|
foreach (var example in ModelKindCorpus.ENTRIES)
|
||||||
{
|
{
|
||||||
@@ -32,7 +32,7 @@ public sealed class ModelKindTests
|
|||||||
|
|
||||||
Assert.That(profile.Kind, Is.EqualTo(example.Kind), $"{example.Provider} \"{example.ModelId}\"");
|
Assert.That(profile.Kind, Is.EqualTo(example.Kind), $"{example.Provider} \"{example.ModelId}\"");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -59,11 +59,11 @@ public sealed class ModelKindTests
|
|||||||
//
|
//
|
||||||
var profile = ModelRegistry.Shared.Profile(LLMProviders.SELF_HOSTED, "llama-guard-3-8b");
|
var profile = ModelRegistry.Shared.Profile(LLMProviders.SELF_HOSTED, "llama-guard-3-8b");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Kind, Is.EqualTo(ModelKind.MODERATION));
|
Assert.That(profile.Kind, Is.EqualTo(ModelKind.MODERATION));
|
||||||
Assert.That(profile.Has(Capability.TEXT_INPUT), Is.True, "The family which chose the model still speaks for it.");
|
Assert.That(profile.Has(Capability.TEXT_INPUT), Is.True, "The family which chose the model still speaks for it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -71,10 +71,10 @@ public sealed class ModelKindTests
|
|||||||
{
|
{
|
||||||
var resolution = ModelRegistry.Shared.Explain(LLMProviders.SELF_HOSTED, "bge-reranker-v2-m3");
|
var resolution = ModelRegistry.Shared.Explain(LLMProviders.SELF_HOSTED, "bge-reranker-v2-m3");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(resolution.Profile.Kind, Is.EqualTo(ModelKind.RERANKING));
|
Assert.That(resolution.Profile.Kind, Is.EqualTo(ModelKind.RERANKING));
|
||||||
Assert.That(resolution.Modifiers.Select(modifier => modifier.Pattern.Text), Does.Contain("bge"), "Both words match; the ranked one has to be the one which gets the last word.");
|
Assert.That(resolution.Modifiers.Select(modifier => modifier.Pattern.Text), Does.Contain("bge"), "Both words match; the ranked one has to be the one which gets the last word.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@ public sealed class ModelProfileTests
|
|||||||
{
|
{
|
||||||
var untouched = ModelProfile.UNKNOWN;
|
var untouched = ModelProfile.UNKNOWN;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(untouched.Capabilities, Is.EqualTo(Capability.NONE));
|
Assert.That(untouched.Capabilities, Is.EqualTo(Capability.NONE));
|
||||||
Assert.That(untouched.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(untouched.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
@@ -27,7 +27,7 @@ public sealed class ModelProfileTests
|
|||||||
// meaning "no idea".
|
// meaning "no idea".
|
||||||
//
|
//
|
||||||
Assert.That(untouched.Kind, Is.EqualTo(ModelKind.CHAT));
|
Assert.That(untouched.Kind, Is.EqualTo(ModelKind.CHAT));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -35,13 +35,13 @@ public sealed class ModelProfileTests
|
|||||||
{
|
{
|
||||||
var profile = new ModelProfile { Capabilities = Capability.TEXT_INPUT | Capability.TEXT_OUTPUT };
|
var profile = new ModelProfile { Capabilities = Capability.TEXT_INPUT | Capability.TEXT_OUTPUT };
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.TEXT_INPUT | Capability.TEXT_OUTPUT), Is.True);
|
Assert.That(profile.Has(Capability.TEXT_INPUT | Capability.TEXT_OUTPUT), Is.True);
|
||||||
Assert.That(profile.Has(Capability.TEXT_INPUT | Capability.WEB_SEARCH), Is.False);
|
Assert.That(profile.Has(Capability.TEXT_INPUT | Capability.WEB_SEARCH), Is.False);
|
||||||
Assert.That(profile.HasAny(Capability.TEXT_INPUT | Capability.WEB_SEARCH), Is.True);
|
Assert.That(profile.HasAny(Capability.TEXT_INPUT | Capability.WEB_SEARCH), Is.True);
|
||||||
Assert.That(profile.HasAny(Capability.WEB_SEARCH | Capability.EMBEDDING), Is.False);
|
Assert.That(profile.HasAny(Capability.WEB_SEARCH | Capability.EMBEDDING), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -69,13 +69,13 @@ public sealed class ModelProfileTests
|
|||||||
|
|
||||||
var after = new ModelProfileChange { Removes = Capability.WEB_SEARCH }.ApplyTo(before);
|
var after = new ModelProfileChange { Removes = Capability.WEB_SEARCH }.ApplyTo(before);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(after.Capabilities, Is.EqualTo(Capability.TEXT_INPUT));
|
Assert.That(after.Capabilities, Is.EqualTo(Capability.TEXT_INPUT));
|
||||||
Assert.That(after.Reasoning, Is.EqualTo(ReasoningSupport.OPTIONAL), "A change saying nothing about reasoning must not reset it.");
|
Assert.That(after.Reasoning, Is.EqualTo(ReasoningSupport.OPTIONAL), "A change saying nothing about reasoning must not reset it.");
|
||||||
Assert.That(after.Context, Is.EqualTo(before.Context), "A change saying nothing about the context window must not reset it.");
|
Assert.That(after.Context, Is.EqualTo(before.Context), "A change saying nothing about the context window must not reset it.");
|
||||||
Assert.That(after.SystemPromptRole, Is.EqualTo(SystemPromptRole.DEVELOPER), "A change saying nothing about the system prompt role must not reset it.");
|
Assert.That(after.SystemPromptRole, Is.EqualTo(SystemPromptRole.DEVELOPER), "A change saying nothing about the system prompt role must not reset it.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -106,11 +106,11 @@ public sealed class ModelProfileTests
|
|||||||
|
|
||||||
var profile = change.ApplyTo(ModelProfile.UNKNOWN);
|
var profile = change.ApplyTo(ModelProfile.UNKNOWN);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Capabilities, Is.EqualTo(Capability.TEXT_INPUT));
|
Assert.That(profile.Capabilities, Is.EqualTo(Capability.TEXT_INPUT));
|
||||||
Assert.That(profile.Has(Capability.ALWAYS_REASONING), Is.False);
|
Assert.That(profile.Has(Capability.ALWAYS_REASONING), Is.False);
|
||||||
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.ALWAYS));
|
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.ALWAYS));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ public sealed class Gpt6FamilyTests
|
|||||||
{
|
{
|
||||||
var profile = LLMProviders.OPEN_AI.GetModelProfile(new Model(modelId, null));
|
var profile = LLMProviders.OPEN_AI.GetModelProfile(new Model(modelId, null));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.RESPONSES_API), Is.True);
|
Assert.That(profile.Has(Capability.RESPONSES_API), Is.True);
|
||||||
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
||||||
@@ -35,7 +35,7 @@ public sealed class Gpt6FamilyTests
|
|||||||
Assert.That(profile.Has(Capability.WEB_SEARCH), Is.True);
|
Assert.That(profile.Has(Capability.WEB_SEARCH), Is.True);
|
||||||
Assert.That(profile.Context.IsKnown, Is.True);
|
Assert.That(profile.Context.IsKnown, Is.True);
|
||||||
Assert.That(profile.Context.DefaultTokens, Is.EqualTo(1_050_000));
|
Assert.That(profile.Context.DefaultTokens, Is.EqualTo(1_050_000));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase("gpt-6-astra", ReasoningSupport.ALWAYS, TestName = "Astra reasons on every request")]
|
[TestCase("gpt-6-astra", ReasoningSupport.ALWAYS, TestName = "Astra reasons on every request")]
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ public sealed class NewOpenAIModelTests
|
|||||||
{
|
{
|
||||||
var profile = LLMProviders.OPEN_AI.GetModelProfile(new Model(modelId, null));
|
var profile = LLMProviders.OPEN_AI.GetModelProfile(new Model(modelId, null));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.RESPONSES_API), Is.True);
|
Assert.That(profile.Has(Capability.RESPONSES_API), Is.True);
|
||||||
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.True);
|
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.True);
|
||||||
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
||||||
Assert.That(profile.Reasoning, Is.Not.EqualTo(ReasoningSupport.NONE));
|
Assert.That(profile.Reasoning, Is.Not.EqualTo(ReasoningSupport.NONE));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -46,12 +46,12 @@ public sealed class NewOpenAIModelTests
|
|||||||
//
|
//
|
||||||
var profile = LLMProviders.OPEN_AI.GetModelProfile(new Model("gpt-oss-120b", null));
|
var profile = LLMProviders.OPEN_AI.GetModelProfile(new Model("gpt-oss-120b", null));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.RESPONSES_API), Is.False);
|
Assert.That(profile.Has(Capability.RESPONSES_API), Is.False);
|
||||||
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
Assert.That(profile.Context.DefaultTokens, Is.EqualTo(131_072));
|
Assert.That(profile.Context.DefaultTokens, Is.EqualTo(131_072));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -59,11 +59,11 @@ public sealed class NewOpenAIModelTests
|
|||||||
{
|
{
|
||||||
var profile = LLMProviders.SELF_HOSTED.GetModelProfile(new Model("gpt-j-6b", null));
|
var profile = LLMProviders.SELF_HOSTED.GetModelProfile(new Model("gpt-j-6b", null));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.RESPONSES_API), Is.False);
|
Assert.That(profile.Has(Capability.RESPONSES_API), Is.False);
|
||||||
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
Assert.That(profile.Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
||||||
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,11 +49,11 @@ public sealed class DeclaredModelsTests
|
|||||||
|
|
||||||
var profile = registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-7b");
|
var profile = registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-7b");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.False, "The built-in rule grants this one, and the declaration does not.");
|
Assert.That(profile.Has(Capability.FUNCTION_CALLING), Is.False, "The built-in rule grants this one, and the declaration does not.");
|
||||||
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE), "Nor does it reason, whatever the built-in rule says.");
|
Assert.That(profile.Reasoning, Is.EqualTo(ReasoningSupport.NONE), "Nor does it reason, whatever the built-in rule says.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -83,11 +83,11 @@ public sealed class DeclaredModelsTests
|
|||||||
registry.Declare([]);
|
registry.Declare([]);
|
||||||
var afterwards = registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-7b");
|
var afterwards = registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-7b");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(whileDeclared.Has(Capability.FUNCTION_CALLING), Is.False);
|
Assert.That(whileDeclared.Has(Capability.FUNCTION_CALLING), Is.False);
|
||||||
Assert.That(afterwards.Has(Capability.FUNCTION_CALLING), Is.True);
|
Assert.That(afterwards.Has(Capability.FUNCTION_CALLING), Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -105,11 +105,11 @@ public sealed class DeclaredModelsTests
|
|||||||
Declaring("acme-assistant-7b", MatchKind.EXACT, Capability.TEXT_INPUT | Capability.TEXT_OUTPUT | Capability.MULTIPLE_IMAGE_INPUT),
|
Declaring("acme-assistant-7b", MatchKind.EXACT, Capability.TEXT_INPUT | Capability.TEXT_OUTPUT | Capability.MULTIPLE_IMAGE_INPUT),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-7b").Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
Assert.That(registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-7b").Has(Capability.MULTIPLE_IMAGE_INPUT), Is.True);
|
||||||
Assert.That(registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-3b").Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
Assert.That(registry.Profile(LLMProviders.SELF_HOSTED, "acme-assistant-3b").Has(Capability.MULTIPLE_IMAGE_INPUT), Is.False);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -127,11 +127,11 @@ public sealed class DeclaredModelsTests
|
|||||||
{
|
{
|
||||||
registry.Declare([declaration]);
|
registry.Declare([declaration]);
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(registry.Profile(LLMProviders.OPEN_AI, "gpt-5.1").Has(Capability.VIDEO_INPUT), Is.True);
|
Assert.That(registry.Profile(LLMProviders.OPEN_AI, "gpt-5.1").Has(Capability.VIDEO_INPUT), Is.True);
|
||||||
Assert.That(registry.Profile(LLMProviders.OPEN_ROUTER, "openai/gpt-5.1").Has(Capability.VIDEO_INPUT), Is.True, "The same model, reached through a gateway which wraps the name.");
|
Assert.That(registry.Profile(LLMProviders.OPEN_ROUTER, "openai/gpt-5.1").Has(Capability.VIDEO_INPUT), Is.True, "The same model, reached through a gateway which wraps the name.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public sealed class ModelDeclarationTests
|
|||||||
var declaration = await ReadAsync(A_COMPLETE_DECLARATION);
|
var declaration = await ReadAsync(A_COMPLETE_DECLARATION);
|
||||||
|
|
||||||
Assert.That(declaration, Is.Not.Null);
|
Assert.That(declaration, Is.Not.Null);
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(declaration!.Pattern.Text, Is.EqualTo("acme-assistant"));
|
Assert.That(declaration!.Pattern.Text, Is.EqualTo("acme-assistant"));
|
||||||
Assert.That(declaration.Pattern.Kind, Is.EqualTo(MatchKind.PREFIX));
|
Assert.That(declaration.Pattern.Kind, Is.EqualTo(MatchKind.PREFIX));
|
||||||
@@ -68,7 +68,7 @@ public sealed class ModelDeclarationTests
|
|||||||
Assert.That(declaration.Change.Images, Is.EqualTo(new ImageLimits(1, 8)));
|
Assert.That(declaration.Change.Images, Is.EqualTo(new ImageLimits(1, 8)));
|
||||||
Assert.That(declaration.Source.CheckedOn, Is.EqualTo(new DateOnly(2026, 9, 12)));
|
Assert.That(declaration.Source.CheckedOn, Is.EqualTo(new DateOnly(2026, 9, 12)));
|
||||||
Assert.That(declaration.EnterpriseConfigurationPluginId, Is.EqualTo(PLUGIN_ID));
|
Assert.That(declaration.EnterpriseConfigurationPluginId, Is.EqualTo(PLUGIN_ID));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -77,7 +77,7 @@ public sealed class ModelDeclarationTests
|
|||||||
var declaration = await ReadAsync(THE_LEAST_A_DECLARATION_CAN_SAY);
|
var declaration = await ReadAsync(THE_LEAST_A_DECLARATION_CAN_SAY);
|
||||||
|
|
||||||
Assert.That(declaration, Is.Not.Null);
|
Assert.That(declaration, Is.Not.Null);
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(declaration!.Pattern.Kind, Is.EqualTo(MatchKind.SEGMENT), "The kind to reach for by default, here as everywhere else.");
|
Assert.That(declaration!.Pattern.Kind, Is.EqualTo(MatchKind.SEGMENT), "The kind to reach for by default, here as everywhere else.");
|
||||||
Assert.That(declaration.Change.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
Assert.That(declaration.Change.Reasoning, Is.EqualTo(ReasoningSupport.NONE));
|
||||||
@@ -85,7 +85,7 @@ public sealed class ModelDeclarationTests
|
|||||||
Assert.That(declaration.Change.Context, Is.Null);
|
Assert.That(declaration.Change.Context, Is.Null);
|
||||||
Assert.That(declaration.Change.Tokenizer, Is.Null);
|
Assert.That(declaration.Change.Tokenizer, Is.Null);
|
||||||
Assert.That(declaration.Change.Images, Is.Null);
|
Assert.That(declaration.Change.Images, Is.Null);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -199,11 +199,11 @@ public sealed class ModelDeclarationTests
|
|||||||
var theSameNames = await ReadAsync(A_COMPLETE_DECLARATION.Replace("""["CONTEXT_WINDOW"] = 131072,""", """["CONTEXT_WINDOW"] = 65536,""", StringComparison.Ordinal));
|
var theSameNames = await ReadAsync(A_COMPLETE_DECLARATION.Replace("""["CONTEXT_WINDOW"] = 131072,""", """["CONTEXT_WINDOW"] = 65536,""", StringComparison.Ordinal));
|
||||||
var otherNames = await ReadAsync(A_COMPLETE_DECLARATION.Replace("""["MATCH"] = "PREFIX",""", """["MATCH"] = "SEGMENT",""", StringComparison.Ordinal));
|
var otherNames = await ReadAsync(A_COMPLETE_DECLARATION.Replace("""["MATCH"] = "PREFIX",""", """["MATCH"] = "SEGMENT",""", StringComparison.Ordinal));
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(theSameNames?.Id, Is.EqualTo(declaration?.Id), "The same pattern, so one of the two has to win.");
|
Assert.That(theSameNames?.Id, Is.EqualTo(declaration?.Id), "The same pattern, so one of the two has to win.");
|
||||||
Assert.That(otherNames?.Id, Is.Not.EqualTo(declaration?.Id), "Bound to the name differently, so they claim different sets of names.");
|
Assert.That(otherNames?.Id, Is.Not.EqualTo(declaration?.Id), "Bound to the name differently, so they claim different sets of names.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<ModelDeclaration?> ReadAsync(string entry)
|
private static async Task<ModelDeclaration?> ReadAsync(string entry)
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public sealed class PortingDifferenceTests
|
|||||||
{
|
{
|
||||||
var corrected = ExpectedChanges.ENTRIES.Where(change => IsAnswered(change.Provider, change.ModelId)).ToList();
|
var corrected = ExpectedChanges.ENTRIES.Where(change => IsAnswered(change.Provider, change.ModelId)).ToList();
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(corrected, Is.Not.Empty, "Nothing the audit found wrong is answered by a rule at all, which would make this test green for the wrong reason.");
|
Assert.That(corrected, Is.Not.Empty, "Nothing the audit found wrong is answered by a rule at all, which would make this test green for the wrong reason.");
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ public sealed class PortingDifferenceTests
|
|||||||
|
|
||||||
Assert.That(rebuilt, Is.EqualTo(CapabilitySnapshot.Describe(change.AnswerWanted)), $"{change.Provider} \"{change.ModelId}\": {change.Reason}");
|
Assert.That(rebuilt, Is.EqualTo(CapabilitySnapshot.Describe(change.AnswerWanted)), $"{change.Provider} \"{change.ModelId}\": {change.Reason}");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -80,7 +80,7 @@ public sealed class PortingDifferenceTests
|
|||||||
// toss. Reading the rules alone cannot find it -- the two patterns are written differently
|
// toss. Reading the rules alone cannot find it -- the two patterns are written differently
|
||||||
// and only meet on a real name, which is what the corpus is full of.
|
// and only meet on a real name, which is what the corpus is full of.
|
||||||
//
|
//
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
foreach (var entry in ModelCorpus.ENTRIES)
|
foreach (var entry in ModelCorpus.ENTRIES)
|
||||||
{
|
{
|
||||||
@@ -88,7 +88,7 @@ public sealed class PortingDifferenceTests
|
|||||||
|
|
||||||
Assert.That(resolution.IsAmbiguous, Is.False, $"{entry.Provider} \"{entry.ModelId}\" is claimed by {resolution.Selector} and, just as strongly, by {string.Join(", ", resolution.TiedSelectors)}.");
|
Assert.That(resolution.IsAmbiguous, Is.False, $"{entry.Provider} \"{entry.ModelId}\" is claimed by {resolution.Selector} and, just as strongly, by {string.Join(", ", resolution.TiedSelectors)}.");
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -114,12 +114,12 @@ public sealed class ModelRegistryTests
|
|||||||
var atOpenAI = ModelRegistry.Shared.Profile(LLMProviders.OPEN_AI, "gpt-5.1");
|
var atOpenAI = ModelRegistry.Shared.Profile(LLMProviders.OPEN_AI, "gpt-5.1");
|
||||||
var throughAGateway = ModelRegistry.Shared.Profile(LLMProviders.OPEN_ROUTER, "openai/gpt-5.1");
|
var throughAGateway = ModelRegistry.Shared.Profile(LLMProviders.OPEN_ROUTER, "openai/gpt-5.1");
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(atOpenAI.Has(Capability.RESPONSES_API), Is.True);
|
Assert.That(atOpenAI.Has(Capability.RESPONSES_API), Is.True);
|
||||||
Assert.That(throughAGateway.Has(Capability.RESPONSES_API), Is.False);
|
Assert.That(throughAGateway.Has(Capability.RESPONSES_API), Is.False);
|
||||||
Assert.That(throughAGateway.Has(Capability.FUNCTION_CALLING), Is.True, "Everything but the API survives the trip through a gateway.");
|
Assert.That(throughAGateway.Has(Capability.FUNCTION_CALLING), Is.True, "Everything but the API survives the trip through a gateway.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
|||||||
@@ -40,11 +40,11 @@ public sealed class TokenizerRuleTests
|
|||||||
{
|
{
|
||||||
var tokenizer = provider.GetModelProfile(new Model(modelId, null)).Tokenizer;
|
var tokenizer = provider.GetModelProfile(new Model(modelId, null)).Tokenizer;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(tokenizer.Kind, Is.EqualTo(TokenizerKind.TIKTOKEN));
|
Assert.That(tokenizer.Kind, Is.EqualTo(TokenizerKind.TIKTOKEN));
|
||||||
Assert.That(tokenizer.Id, Is.EqualTo(encoding));
|
Assert.That(tokenizer.Id, Is.EqualTo(encoding));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(LLMProviders.ANTHROPIC, "claude-opus-5", "/v1/messages/count_tokens")]
|
[TestCase(LLMProviders.ANTHROPIC, "claude-opus-5", "/v1/messages/count_tokens")]
|
||||||
@@ -55,11 +55,11 @@ public sealed class TokenizerRuleTests
|
|||||||
{
|
{
|
||||||
var tokenizer = provider.GetModelProfile(new Model(modelId, null)).Tokenizer;
|
var tokenizer = provider.GetModelProfile(new Model(modelId, null)).Tokenizer;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(tokenizer.Kind, Is.EqualTo(TokenizerKind.PROVIDER_API));
|
Assert.That(tokenizer.Kind, Is.EqualTo(TokenizerKind.PROVIDER_API));
|
||||||
Assert.That(tokenizer.Id, Is.EqualTo(endpoint));
|
Assert.That(tokenizer.Id, Is.EqualTo(endpoint));
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestCase(LLMProviders.OPEN_AI, "gpt-6-astra", Description = "Newer than the mapping OpenAI publishes, so nothing is claimed for it.")]
|
[TestCase(LLMProviders.OPEN_AI, "gpt-6-astra", Description = "Newer than the mapping OpenAI publishes, so nothing is claimed for it.")]
|
||||||
@@ -70,11 +70,11 @@ public sealed class TokenizerRuleTests
|
|||||||
{
|
{
|
||||||
var tokenizer = provider.GetModelProfile(new Model(modelId, null)).Tokenizer;
|
var tokenizer = provider.GetModelProfile(new Model(modelId, null)).Tokenizer;
|
||||||
|
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(tokenizer.IsKnown, Is.False);
|
Assert.That(tokenizer.IsKnown, Is.False);
|
||||||
Assert.That(tokenizer.Kind, Is.EqualTo(TokenizerKind.UNKNOWN), "Which means the built-in tokenizer, the same as today.");
|
Assert.That(tokenizer.Kind, Is.EqualTo(TokenizerKind.UNKNOWN), "Which means the built-in tokenizer, the same as today.");
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@@ -98,12 +98,12 @@ public sealed class TokenizerRuleTests
|
|||||||
// without a kind cannot be told apart from any other string -- whether it is a repository,
|
// without a kind cannot be told apart from any other string -- whether it is a repository,
|
||||||
// an encoding or an endpoint decides what a person can do with it.
|
// an encoding or an endpoint decides what a person can do with it.
|
||||||
//
|
//
|
||||||
Assert.Multiple(() =>
|
using (Assert.EnterMultipleScope())
|
||||||
{
|
{
|
||||||
Assert.That(new TokenizerRef(TokenizerKind.HUGGING_FACE, string.Empty).IsKnown, Is.False);
|
Assert.That(new TokenizerRef(TokenizerKind.HUGGING_FACE, string.Empty).IsKnown, Is.False);
|
||||||
Assert.That(new TokenizerRef(TokenizerKind.UNKNOWN, "o200k_base").IsKnown, Is.False);
|
Assert.That(new TokenizerRef(TokenizerKind.UNKNOWN, "o200k_base").IsKnown, Is.False);
|
||||||
Assert.That(TokenizerRef.UNKNOWN.IsKnown, Is.False);
|
Assert.That(TokenizerRef.UNKNOWN.IsKnown, Is.False);
|
||||||
Assert.That(new TokenizerRef(TokenizerKind.TIKTOKEN, "o200k_base").IsKnown, Is.True);
|
Assert.That(new TokenizerRef(TokenizerKind.TIKTOKEN, "o200k_base").IsKnown, Is.True);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loaded 100 of 327 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user