Skip to content

Commit f947ce1

Browse files
stuff
1 parent cf817ba commit f947ce1

12 files changed

Lines changed: 180 additions & 110 deletions

File tree

.github/workflows/ci.yml

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,56 @@ jobs:
1515
ci:
1616
name: CI
1717
runs-on: ubuntu-latest
18-
timeout-minutes: 10
18+
timeout-minutes: 30
19+
services:
20+
postgres:
21+
image: pgvector/pgvector:pg16
22+
env:
23+
POSTGRES_USER: postgres
24+
POSTGRES_PASSWORD: changeme
25+
ports:
26+
- 5432:5432
27+
options: >-
28+
--health-cmd "pg_isready -U postgres"
29+
--health-interval 5s
30+
--health-timeout 3s
31+
--health-retries 10
32+
env:
33+
TEST_POSTGRES_CONNECTION: Host=localhost;Database=postgres;Username=postgres;Password=changeme
34+
ICD10_TEST_CONNECTION_STRING: Host=localhost;Database=postgres;Username=postgres;Password=changeme
1935
steps:
2036
- uses: actions/checkout@v4
2137

2238
- uses: actions/setup-dotnet@v4
2339
with:
2440
dotnet-version: '10.0.x'
2541

42+
- name: Start embedding service
43+
run: |
44+
cd ICD10/embedding-service
45+
docker compose up -d --build
46+
# Wait until /health responds 200 (model load can take ~60s)
47+
for i in $(seq 1 60); do
48+
if curl -sf http://localhost:8000/health > /dev/null; then
49+
echo "Embedding service ready"
50+
exit 0
51+
fi
52+
sleep 2
53+
done
54+
echo "Embedding service failed to become healthy"
55+
docker compose logs
56+
exit 1
57+
2658
- run: dotnet restore
2759
- run: dotnet tool restore
2860

61+
- name: Install Playwright browsers
62+
run: |
63+
dotnet build Dashboard/Dashboard.Integration.Tests/Dashboard.Integration.Tests.csproj --configuration Release
64+
dotnet tool install --global Microsoft.Playwright.CLI || true
65+
export PATH="$PATH:$HOME/.dotnet/tools"
66+
playwright install --with-deps chromium
67+
2968
- name: Lint
3069
run: make lint
3170

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ artifacts/
7979
project.lock.json
8080
*.nupkg
8181
*.snupkg
82+
!nupkgs/*.nupkg
8283
**/packages/*
8384
!**/packages/build/
8485
!**/packages/repositories.config

Dashboard/Dashboard.Integration.Tests/Dashboard.Integration.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<ProjectReference Include="../../Scheduling/Scheduling.Api/Scheduling.Api.csproj" />
3030
<ProjectReference Include="../../Scheduling/Scheduling.Sync/Scheduling.Sync.csproj" />
3131
<ProjectReference Include="../../Gatekeeper/Gatekeeper.Api/Gatekeeper.Api.csproj" />
32+
<ProjectReference Include="../../ICD10/ICD10.TestSupport/ICD10.TestSupport.csproj" />
3233
</ItemGroup>
3334

3435
<!-- Copy Dashboard.Web wwwroot for Playwright tests -->

Dashboard/Dashboard.Integration.Tests/E2EFixture.cs

Lines changed: 28 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Security.Cryptography;
44
using System.Text;
55
using System.Text.Json;
6+
using ICD10.TestSupport;
67
using Microsoft.AspNetCore.Builder;
78
using Microsoft.AspNetCore.Hosting;
89
using Microsoft.AspNetCore.Hosting.Server;
@@ -141,14 +142,28 @@ await Task.WhenAll(
141142
var samplesDir = Path.GetFullPath(
142143
Path.Combine(testAssemblyDir, "..", "..", "..", "..", "..")
143144
);
144-
var rootDir = Path.GetFullPath(Path.Combine(samplesDir, ".."));
145145

146-
// Run ICD-10 migration and import official CDC data
147-
await SetupIcd10DatabaseAsync(icd10ConnStr, samplesDir, rootDir);
146+
// Run ICD-10 migration and import official CDC data.
147+
// ICD-10 is optional in the E2E suite (see ICD-10 API skip block below) - if
148+
// setup fails (e.g. embedding service or Python toolchain unavailable), continue
149+
// without it instead of failing the entire fixture.
150+
var icd10Ready = false;
151+
try
152+
{
153+
await SetupIcd10DatabaseAsync(icd10ConnStr, samplesDir);
154+
icd10Ready = true;
155+
}
156+
catch (Exception ex)
157+
{
158+
Console.WriteLine(
159+
$"[E2E] WARNING: ICD-10 database setup failed ({ex.Message}); "
160+
+ "ICD-10 dependent tests will be skipped"
161+
);
162+
}
148163

149164
var clinicalProjectDir = Path.Combine(samplesDir, "Clinical", "Clinical.Api");
150165
var schedulingProjectDir = Path.Combine(samplesDir, "Scheduling", "Scheduling.Api");
151-
var gatekeeperProjectDir = Path.Combine(rootDir, "Gatekeeper", "Gatekeeper.Api");
166+
var gatekeeperProjectDir = Path.Combine(samplesDir, "Gatekeeper", "Gatekeeper.Api");
152167
var icd10ProjectDir = Path.Combine(samplesDir, "ICD10", "ICD10.Api");
153168
var configuration = ResolveBuildConfiguration(testAssemblyDir);
154169

@@ -226,12 +241,12 @@ await Task.WhenAll(
226241
["ConnectionStrings__Postgres"] = icd10ConnStr,
227242
["ConnectionStrings__DefaultConnection"] = icd10ConnStr,
228243
};
229-
if (File.Exists(icd10Dll))
244+
if (icd10Ready && File.Exists(icd10Dll))
230245
{
231246
_icd10Process = StartApiFromDll(icd10Dll, icd10ProjectDir, Icd10Url, icd10Env);
232247
Console.WriteLine($"[E2E] ICD-10 API starting on {Icd10Url}");
233248
}
234-
else
249+
else if (!File.Exists(icd10Dll))
235250
{
236251
Console.WriteLine($"[E2E] ICD-10 API DLL missing: {icd10Dll}");
237252
}
@@ -1040,117 +1055,27 @@ private static async Task SeedAsync(HttpClient client, string url, string json)
10401055
/// Sets up the ICD-10 database by running migration and importing official CDC data.
10411056
/// Skips import if data already exists in the database.
10421057
/// </summary>
1043-
private static async Task SetupIcd10DatabaseAsync(
1044-
string connectionString,
1045-
string samplesDir,
1046-
string rootDir
1047-
)
1058+
private static async Task SetupIcd10DatabaseAsync(string connectionString, string samplesDir)
10481059
{
10491060
Console.WriteLine("[E2E] Setting up ICD-10 database...");
10501061

10511062
var icd10ProjectDir = Path.Combine(samplesDir, "ICD10", "ICD10.Api");
10521063
var schemaPath = Path.Combine(icd10ProjectDir, "icd10-schema.yaml");
1053-
var migrationCliDir = Path.Combine(rootDir, "Migration", "Migration.Cli");
1054-
var scriptsDir = Path.Combine(samplesDir, "ICD10", "scripts", "CreateDb");
10551064

10561065
// Check if schema already exists and has data
10571066
if (await Icd10DatabaseHasDataAsync(connectionString))
10581067
{
10591068
Console.WriteLine(
1060-
"[E2E] ICD-10 database already has data - skipping migration and import"
1069+
"[E2E] ICD-10 database already has data - skipping migration and seed"
10611070
);
10621071
return;
10631072
}
10641073

1065-
// Step 1: Run migration to create schema
1066-
Console.WriteLine("[E2E] Running ICD-10 schema migration...");
1067-
var configuration = ResolveBuildConfiguration(
1068-
Path.GetDirectoryName(typeof(E2EFixture).Assembly.Location)!
1069-
);
1070-
var migrationDll = Path.Combine(
1071-
migrationCliDir,
1072-
"bin",
1073-
configuration,
1074-
"net10.0",
1075-
"Migration.Cli.dll"
1076-
);
1077-
1078-
int migrationResult;
1079-
if (File.Exists(migrationDll))
1080-
{
1081-
Console.WriteLine($"[E2E] Using pre-built Migration.Cli: {migrationDll}");
1082-
migrationResult = await RunProcessAsync(
1083-
"dotnet",
1084-
$"exec \"{migrationDll}\" --schema \"{schemaPath}\" --output \"{connectionString}\" --provider postgres",
1085-
rootDir,
1086-
timeoutMs: 600_000
1087-
);
1088-
}
1089-
else
1090-
{
1091-
Console.WriteLine(
1092-
$"[E2E] Migration.Cli DLL not found at {migrationDll}, falling back to dotnet run"
1093-
);
1094-
migrationResult = await RunProcessAsync(
1095-
"dotnet",
1096-
$"run --project \"{migrationCliDir}\" -- --schema \"{schemaPath}\" --output \"{connectionString}\" --provider postgres",
1097-
rootDir,
1098-
timeoutMs: 600_000
1099-
);
1100-
}
1101-
1102-
if (migrationResult != 0)
1103-
{
1104-
throw new Exception($"ICD-10 migration failed with exit code {migrationResult}");
1105-
}
1106-
1107-
Console.WriteLine("[E2E] ICD-10 schema created successfully");
1108-
1109-
// Step 2: Set up Python virtual environment
1110-
var venvDir = Path.Combine(samplesDir, "ICD10", ".venv");
1111-
var pythonScript = Path.Combine(scriptsDir, "import_postgres.py");
1112-
1113-
if (!File.Exists(pythonScript))
1114-
{
1115-
throw new FileNotFoundException($"ICD-10 import script not found: {pythonScript}");
1116-
}
1117-
1118-
Console.WriteLine("[E2E] Setting up Python environment...");
1119-
if (!Directory.Exists(venvDir))
1120-
{
1121-
var venvResult = await RunProcessAsync("python3", $"-m venv \"{venvDir}\"", scriptsDir);
1122-
if (venvResult != 0)
1123-
{
1124-
throw new Exception($"Failed to create Python virtual environment");
1125-
}
1126-
}
1127-
1128-
// Install requirements
1129-
var requirementsPath = Path.Combine(scriptsDir, "requirements.txt");
1130-
var pipResult = await RunProcessAsync(
1131-
$"{venvDir}/bin/pip",
1132-
$"install -r \"{requirementsPath}\"",
1133-
scriptsDir
1134-
);
1135-
if (pipResult != 0)
1136-
{
1137-
throw new Exception($"Failed to install Python dependencies");
1138-
}
1139-
1140-
// Step 3: Import official CDC ICD-10 data
1141-
Console.WriteLine("[E2E] Importing official CDC ICD-10 data...");
1142-
var importResult = await RunProcessAsync(
1143-
$"{venvDir}/bin/python",
1144-
$"\"{pythonScript}\" --connection-string \"{connectionString}\"",
1145-
scriptsDir,
1146-
timeoutMs: 600_000
1147-
);
1148-
1149-
if (importResult != 0)
1150-
{
1151-
throw new Exception($"ICD-10 data import failed with exit code {importResult}");
1152-
}
1153-
1074+
// Apply schema and seed deterministic E2E reference data via the shared
1075+
// ICD10.TestSupport library. This avoids the ~3-minute Python CDC import
1076+
// (44k codes + embeddings) that previously made every dashboard run hang.
1077+
Console.WriteLine("[E2E] Applying ICD-10 schema and seeding test data...");
1078+
await Task.Run(() => Icd10TestDatabase.Initialize(connectionString, schemaPath));
11541079
Console.WriteLine("[E2E] ICD-10 database setup complete");
11551080
}
11561081

HealthcareSamples.sln

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gatekeeper.Api", "Gatekeepe
4545
EndProject
4646
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gatekeeper.Api.Tests", "Gatekeeper\Gatekeeper.Api.Tests\Gatekeeper.Api.Tests.csproj", "{0FC88CC8-1203-4215-AEFC-6CFA0A8DB358}"
4747
EndProject
48+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICD10.TestSupport", "ICD10\ICD10.TestSupport\ICD10.TestSupport.csproj", "{817E658D-F40C-43E5-8D25-92FE882D1760}"
49+
EndProject
4850
Global
4951
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5052
Debug|Any CPU = Debug|Any CPU
@@ -235,6 +237,18 @@ Global
235237
{0FC88CC8-1203-4215-AEFC-6CFA0A8DB358}.Release|x64.Build.0 = Release|Any CPU
236238
{0FC88CC8-1203-4215-AEFC-6CFA0A8DB358}.Release|x86.ActiveCfg = Release|Any CPU
237239
{0FC88CC8-1203-4215-AEFC-6CFA0A8DB358}.Release|x86.Build.0 = Release|Any CPU
240+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
241+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Debug|Any CPU.Build.0 = Debug|Any CPU
242+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Debug|x64.ActiveCfg = Debug|Any CPU
243+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Debug|x64.Build.0 = Debug|Any CPU
244+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Debug|x86.ActiveCfg = Debug|Any CPU
245+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Debug|x86.Build.0 = Debug|Any CPU
246+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Release|Any CPU.ActiveCfg = Release|Any CPU
247+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Release|Any CPU.Build.0 = Release|Any CPU
248+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Release|x64.ActiveCfg = Release|Any CPU
249+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Release|x64.Build.0 = Release|Any CPU
250+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Release|x86.ActiveCfg = Release|Any CPU
251+
{817E658D-F40C-43E5-8D25-92FE882D1760}.Release|x86.Build.0 = Release|Any CPU
238252
EndGlobalSection
239253
GlobalSection(SolutionProperties) = preSolution
240254
HideSolutionNode = FALSE
@@ -255,5 +269,6 @@ Global
255269
{CA395494-F072-4A5B-9DD4-950530A69E0E} = {A1B2C3D4-0001-0001-0001-000000000005}
256270
{3A6684C8-1A85-4BF7-8B5C-E07F4E123F12} = {048F5F03-6DDC-C04F-70D5-B8139DC8E373}
257271
{0FC88CC8-1203-4215-AEFC-6CFA0A8DB358} = {048F5F03-6DDC-C04F-70D5-B8139DC8E373}
272+
{817E658D-F40C-43E5-8D25-92FE882D1760} = {A1B2C3D4-0001-0001-0001-000000000003}
258273
EndGlobalSection
259274
EndGlobal

ICD10/ICD10.Api.Tests/ICD10.Api.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
<ItemGroup>
2525
<ProjectReference Include="..\ICD10.Api\ICD10.Api.csproj" />
26+
<ProjectReference Include="..\ICD10.TestSupport\ICD10.TestSupport.csproj" />
2627
<ProjectReference Include="..\..\Shared\Authorization\Authorization.csproj" />
2728
</ItemGroup>
2829
</Project>

ICD10/ICD10.Api.Tests/ICD10ApiFactory.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using ICD10.TestSupport;
12
using Microsoft.AspNetCore.Hosting;
23
using Microsoft.AspNetCore.Mvc.Testing;
34
using Nimblesite.DataProvider.Migration.Core;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<RootNamespace>ICD10.TestSupport</RootNamespace>
4+
<NoWarn>CA1707;CA1062;CA1515;CA2100;CA1812;CA1849</NoWarn>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Npgsql" Version="9.0.2" />
9+
<PackageReference Include="Nimblesite.DataProvider.Migration.Core" Version="0.2.0-beta" />
10+
<PackageReference Include="Nimblesite.DataProvider.Migration.Postgres" Version="0.2.0-beta" />
11+
</ItemGroup>
12+
</Project>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Nimblesite.DataProvider.Migration.Core;
2+
using Nimblesite.DataProvider.Migration.Postgres;
3+
using Npgsql;
4+
5+
namespace ICD10.TestSupport;
6+
7+
/// <summary>
8+
/// Helpers to provision an ICD-10 test database (schema + seed data) without
9+
/// running the heavyweight Python CDC import (~3 minutes for 44k embeddings).
10+
/// </summary>
11+
public static class Icd10TestDatabase
12+
{
13+
/// <summary>
14+
/// Enables pgvector, applies the icd10-schema.yaml schema via the Migration
15+
/// library, then seeds reference data and (if the embedding service at
16+
/// http://localhost:8000 is available) embeddings.
17+
/// </summary>
18+
/// <param name="connectionString">Connection string to a fresh database.</param>
19+
/// <param name="schemaYamlPath">Absolute path to icd10-schema.yaml.</param>
20+
public static void Initialize(string connectionString, string schemaYamlPath)
21+
{
22+
if (!File.Exists(schemaYamlPath))
23+
{
24+
throw new FileNotFoundException(
25+
$"icd10-schema.yaml not found at '{schemaYamlPath}'",
26+
schemaYamlPath
27+
);
28+
}
29+
30+
using var conn = new NpgsqlConnection(connectionString);
31+
conn.Open();
32+
33+
using (var cmd = conn.CreateCommand())
34+
{
35+
cmd.CommandText = "CREATE EXTENSION IF NOT EXISTS vector";
36+
cmd.ExecuteNonQuery();
37+
}
38+
39+
var schema = SchemaYamlSerializer.FromYamlFile(schemaYamlPath);
40+
PostgresDdlGenerator.MigrateSchema(conn, schema);
41+
42+
TestDataSeeder.Seed(conn);
43+
TestDataSeeder.SeedEmbeddings(conn);
44+
}
45+
}

0 commit comments

Comments
 (0)