Public Access
ci: validate portable tests on macos
This commit is contained in:
@@ -90,3 +90,61 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
find MeetingAssistant.Tests -type d -name TestResults -print || true
|
find MeetingAssistant.Tests -type d -name TestResults -print || true
|
||||||
find MeetingAssistant.Tests -type f -path "*/TestResults/*" -maxdepth 5 -print || true
|
find MeetingAssistant.Tests -type f -path "*/TestResults/*" -maxdepth 5 -print || true
|
||||||
|
|
||||||
|
macos-build-and-test:
|
||||||
|
runs-on: macos
|
||||||
|
env:
|
||||||
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: "1"
|
||||||
|
DOTNET_NOLOGO: "1"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
|
||||||
|
- name: Verify native macOS runner
|
||||||
|
run: |
|
||||||
|
if [ "$(uname -s)" != "Darwin" ]; then
|
||||||
|
echo "The macos job requires a native Gitea runner registered with the macos:host label."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v6
|
||||||
|
with:
|
||||||
|
dotnet-version: "10.0.x"
|
||||||
|
|
||||||
|
- name: Restore (portable target)
|
||||||
|
run: |
|
||||||
|
dotnet restore MeetingAssistant.Tests/MeetingAssistant.Tests.csproj \
|
||||||
|
-p:EnableWindowsTargeting=true
|
||||||
|
|
||||||
|
- name: Build (portable target and macOS helpers)
|
||||||
|
run: |
|
||||||
|
dotnet build MeetingAssistant.Tests/MeetingAssistant.Tests.csproj \
|
||||||
|
-c Release \
|
||||||
|
-f net10.0 \
|
||||||
|
--no-restore \
|
||||||
|
-p:EnableWindowsTargeting=true \
|
||||||
|
--nologo
|
||||||
|
|
||||||
|
- name: Verify macOS native helpers
|
||||||
|
run: |
|
||||||
|
test -x MeetingAssistant/bin/Release/net10.0/Native/MeetingAssistantAudioCapture.app/Contents/MacOS/macos-meeting-audio-capture
|
||||||
|
test -x MeetingAssistant/bin/Release/net10.0/Native/macos-desktop-controls
|
||||||
|
test -x MeetingAssistant/bin/Release/net10.0/Native/macos-meeting-integrations
|
||||||
|
test -x MeetingAssistant/bin/Release/net10.0/Native/macos-meeting-assistant-launcher
|
||||||
|
|
||||||
|
- name: Run tests on macOS
|
||||||
|
run: |
|
||||||
|
dotnet test MeetingAssistant.Tests/MeetingAssistant.Tests.csproj \
|
||||||
|
-c Release \
|
||||||
|
-f net10.0 \
|
||||||
|
--no-build \
|
||||||
|
-p:EnableWindowsTargeting=true \
|
||||||
|
--nologo
|
||||||
|
|
||||||
|
- name: Show test output folder on failure
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
find MeetingAssistant.Tests -type d -name TestResults -print || true
|
||||||
|
find MeetingAssistant.Tests -type f -path "*/TestResults/*" -maxdepth 5 -print || true
|
||||||
|
|||||||
@@ -110,6 +110,11 @@ public sealed class AzureSpeechStreamingTranscriptionProviderTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ResolveKeyFallsBackToUserEnvironment()
|
public void ResolveKeyFallsBackToUserEnvironment()
|
||||||
{
|
{
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var keyEnv = $"MEETING_ASSISTANT_AZURE_KEY_{Guid.NewGuid():N}";
|
var keyEnv = $"MEETING_ASSISTANT_AZURE_KEY_{Guid.NewGuid():N}";
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ public sealed class MeetingNoteStoreTests
|
|||||||
{
|
{
|
||||||
var link = MeetingNoteActionLinks.CreateSummaryRetryLink(
|
var link = MeetingNoteActionLinks.CreateSummaryRetryLink(
|
||||||
"http://localhost:5090/",
|
"http://localhost:5090/",
|
||||||
"C:\\Vault\\Meetings\\Summaries\\summary with spaces.md");
|
Path.Combine(Path.GetTempPath(), "summary with spaces.md"));
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
"[Retry summary generation](http://localhost:5090/meetings/summary/retry?summaryPath=summary%20with%20spaces.md)",
|
"[Retry summary generation](http://localhost:5090/meetings/summary/retry?summaryPath=summary%20with%20spaces.md)",
|
||||||
|
|||||||
@@ -448,9 +448,14 @@ public sealed class RecordingCoordinatorTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task StartCreatesMeetingNoteLinkedToTranscriptAndOpensIt()
|
public async Task StartCreatesMeetingNoteLinkedToTranscriptAndOpensIt()
|
||||||
{
|
{
|
||||||
|
var vaultRoot = Path.Combine(Path.GetTempPath(), "meeting-assistant-vault");
|
||||||
|
var transcriptPath = Path.Combine(vaultRoot, "Meetings", "Transcripts", "20260519-transcript.md");
|
||||||
|
var meetingNotePath = Path.Combine(vaultRoot, "Meetings", "Notes", "20260519-meeting.md");
|
||||||
|
var assistantContextFolder = Path.Combine(vaultRoot, "Meetings", "Assistant Context");
|
||||||
|
var summariesFolder = Path.Combine(vaultRoot, "Meetings", "Summaries");
|
||||||
var audioSource = new ControlledAudioSource();
|
var audioSource = new ControlledAudioSource();
|
||||||
var transcriptStore = new InMemoryTranscriptStore("C:\\Vault\\Meetings\\Transcripts\\20260519-transcript.md");
|
var transcriptStore = new InMemoryTranscriptStore(transcriptPath);
|
||||||
var noteStore = new InMemoryMeetingNoteStore("C:\\Vault\\Meetings\\Notes\\20260519-meeting.md");
|
var noteStore = new InMemoryMeetingNoteStore(meetingNotePath);
|
||||||
var clock = new ManualMeetingInactivityClock(DateTimeOffset.Parse("2026-05-19T10:03:42+02:00"));
|
var clock = new ManualMeetingInactivityClock(DateTimeOffset.Parse("2026-05-19T10:03:42+02:00"));
|
||||||
var noteOpener = new CapturingMeetingNoteOpener();
|
var noteOpener = new CapturingMeetingNoteOpener();
|
||||||
var artifactStore = new InMemoryMeetingArtifactStore();
|
var artifactStore = new InMemoryMeetingArtifactStore();
|
||||||
@@ -468,25 +473,27 @@ public sealed class RecordingCoordinatorTests
|
|||||||
{
|
{
|
||||||
Vault = new VaultOptions
|
Vault = new VaultOptions
|
||||||
{
|
{
|
||||||
AssistantContextFolder = "C:\\Vault\\Meetings\\Assistant Context",
|
AssistantContextFolder = assistantContextFolder,
|
||||||
SummariesFolder = "C:\\Vault\\Meetings\\Summaries"
|
SummariesFolder = summariesFolder
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
NullLogger<MeetingRecordingCoordinator>.Instance,
|
NullLogger<MeetingRecordingCoordinator>.Instance,
|
||||||
inactivityClock: clock);
|
inactivityClock: clock);
|
||||||
|
|
||||||
var status = await coordinator.StartAsync(CancellationToken.None);
|
var status = await coordinator.StartAsync(CancellationToken.None);
|
||||||
|
var assistantContextPath = Path.Combine(assistantContextFolder, "20260519-1003-context.md");
|
||||||
|
var summaryPath = Path.Combine(summariesFolder, "20260519-1003-summary.md");
|
||||||
|
|
||||||
Assert.True(status.IsRecording);
|
Assert.True(status.IsRecording);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Notes\\20260519-meeting.md", status.MeetingNotePath);
|
Assert.Equal(meetingNotePath, status.MeetingNotePath);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Transcripts\\20260519-transcript.md", noteStore.SavedNote?.Frontmatter.Transcript);
|
Assert.Equal(transcriptPath, noteStore.SavedNote?.Frontmatter.Transcript);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Assistant Context\\20260519-1003-context.md", noteStore.SavedNote?.Frontmatter.AssistantContext);
|
Assert.Equal(assistantContextPath, noteStore.SavedNote?.Frontmatter.AssistantContext);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Summaries\\20260519-1003-summary.md", noteStore.SavedNote?.Frontmatter.Summary);
|
Assert.Equal(summaryPath, noteStore.SavedNote?.Frontmatter.Summary);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Notes\\20260519-meeting.md", noteOpener.OpenedPath);
|
Assert.Equal(meetingNotePath, noteOpener.OpenedPath);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Notes\\20260519-meeting.md", artifactStore.CreatedArtifacts?.MeetingNotePath);
|
Assert.Equal(meetingNotePath, artifactStore.CreatedArtifacts?.MeetingNotePath);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Transcripts\\20260519-transcript.md", artifactStore.CreatedArtifacts?.TranscriptPath);
|
Assert.Equal(transcriptPath, artifactStore.CreatedArtifacts?.TranscriptPath);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Assistant Context\\20260519-1003-context.md", artifactStore.CreatedArtifacts?.AssistantContextPath);
|
Assert.Equal(assistantContextPath, artifactStore.CreatedArtifacts?.AssistantContextPath);
|
||||||
Assert.Equal("C:\\Vault\\Meetings\\Summaries\\20260519-1003-summary.md", artifactStore.CreatedArtifacts?.SummaryPath);
|
Assert.Equal(summaryPath, artifactStore.CreatedArtifacts?.SummaryPath);
|
||||||
Assert.Equal(noteStore.SavedNote?.Frontmatter.Title, artifactStore.ContextMeetingNote?.Frontmatter.Title);
|
Assert.Equal(noteStore.SavedNote?.Frontmatter.Title, artifactStore.ContextMeetingNote?.Frontmatter.Title);
|
||||||
Assert.Equal(noteStore.SavedNote?.Frontmatter.StartTime, artifactStore.ContextMeetingNote?.Frontmatter.StartTime);
|
Assert.Equal(noteStore.SavedNote?.Frontmatter.StartTime, artifactStore.ContextMeetingNote?.Frontmatter.StartTime);
|
||||||
|
|
||||||
|
|||||||
@@ -181,6 +181,11 @@ public sealed class TaskbarIconTests
|
|||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
public void TaskbarIconGlyphsAreVisuallyCentered()
|
public void TaskbarIconGlyphsAreVisuallyCentered()
|
||||||
{
|
{
|
||||||
|
if (!OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var state in new[]
|
foreach (var state in new[]
|
||||||
{
|
{
|
||||||
RecordingProcessState.Idle,
|
RecordingProcessState.Idle,
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ public sealed class VaultPathTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void ResolveUsesVaultBaseFolderForRelativePaths()
|
public void ResolveUsesVaultBaseFolderForRelativePaths()
|
||||||
{
|
{
|
||||||
|
var baseFolder = Path.Combine(Path.GetTempPath(), "meeting-assistant-vault");
|
||||||
var vault = new VaultOptions
|
var vault = new VaultOptions
|
||||||
{
|
{
|
||||||
BaseFolder = @"C:\Users\masc3\OpenCloud\Persönlich\Vault\Exxeta"
|
BaseFolder = baseFolder
|
||||||
};
|
};
|
||||||
|
|
||||||
var resolved = VaultPath.Resolve(vault, @"Meetings\Transcripts");
|
var relativePath = Path.Combine("Meetings", "Transcripts");
|
||||||
|
var resolved = VaultPath.Resolve(vault, relativePath);
|
||||||
|
|
||||||
Assert.Equal(
|
Assert.Equal(Path.Combine(baseFolder, relativePath), resolved);
|
||||||
@"C:\Users\masc3\OpenCloud\Persönlich\Vault\Exxeta\Meetings\Transcripts",
|
|
||||||
resolved);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -24,11 +24,12 @@ public sealed class VaultPathTests
|
|||||||
{
|
{
|
||||||
var vault = new VaultOptions
|
var vault = new VaultOptions
|
||||||
{
|
{
|
||||||
BaseFolder = @"C:\Users\masc3\OpenCloud\Persönlich\Vault\Exxeta"
|
BaseFolder = Path.Combine(Path.GetTempPath(), "meeting-assistant-vault")
|
||||||
};
|
};
|
||||||
|
var absolutePath = Path.Combine(Path.GetTempPath(), "meeting-assistant-other");
|
||||||
|
|
||||||
var resolved = VaultPath.Resolve(vault, @"C:\Other\Path");
|
var resolved = VaultPath.Resolve(vault, absolutePath);
|
||||||
|
|
||||||
Assert.Equal(@"C:\Other\Path", resolved);
|
Assert.Equal(absolutePath, resolved);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ Detailed workflow syntax and extension guidance live in `docs/meeting-workflow-e
|
|||||||
|
|
||||||
Behavior changes are OpenSpec-driven and test-first: update the relevant requirement/scenario, add a failing public behavior test, implement the smallest passing change, run focused tests and then the justified broader suite, and validate the active change with `openspec validate <change-id> --strict`. Documentation-only maintenance does not need a new OpenSpec change.
|
Behavior changes are OpenSpec-driven and test-first: update the relevant requirement/scenario, add a failing public behavior test, implement the smallest passing change, run focused tests and then the justified broader suite, and validate the active change with `openspec validate <change-id> --strict`. Documentation-only maintenance does not need a new OpenSpec change.
|
||||||
|
|
||||||
The Gitea workflow runs for pull requests, pushes, and manual dispatch. It builds the Windows target on an Ubuntu runner, installs Wine plus a matching Windows .NET SDK, and runs the test project through the Windows host under Wine. It validates source; it does not publish or deploy the workstation application.
|
The Gitea workflow runs for pull requests, pushes, and manual dispatch. Its Linux job builds the Windows target, installs Wine plus a matching Windows .NET SDK, and runs the test project through the Windows host under Wine. A separate `runs-on: macos` job builds the portable target and bundled Swift helpers, verifies the native helper executables, and runs the complete portable unit-test suite on macOS. The Gitea runner serving that job must run natively on a Mac and expose the `macos:host` label; Docker-OSX is not used because it requires nested KVM that is normally unavailable on CI runners. CI validates source; it does not publish or deploy the workstation application.
|
||||||
|
|
||||||
## Operations And Limitations
|
## Operations And Limitations
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user