Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<PackageVersion Include="Dapper" Version="2.1.79" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.0.12" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="5.9.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.12" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.12" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.12" />
Expand Down
15 changes: 15 additions & 0 deletions GenWave.sln
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExamplePlugin", "examples\g
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenWave.Ads", "src\GenWave.Ads\GenWave.Ads.csproj", "{60CD65BA-8A92-41D5-BB1A-0D39AA42184B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GenWave.TestSupport", "tests\GenWave.TestSupport\GenWave.TestSupport.csproj", "{9F21C208-B1B7-4203-8034-79493F06BA1C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -331,6 +333,18 @@ Global
{60CD65BA-8A92-41D5-BB1A-0D39AA42184B}.Release|x64.Build.0 = Release|Any CPU
{60CD65BA-8A92-41D5-BB1A-0D39AA42184B}.Release|x86.ActiveCfg = Release|Any CPU
{60CD65BA-8A92-41D5-BB1A-0D39AA42184B}.Release|x86.Build.0 = Release|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Debug|x64.ActiveCfg = Debug|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Debug|x64.Build.0 = Debug|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Debug|x86.ActiveCfg = Debug|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Debug|x86.Build.0 = Debug|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Release|Any CPU.Build.0 = Release|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Release|x64.ActiveCfg = Release|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Release|x64.Build.0 = Release|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Release|x86.ActiveCfg = Release|Any CPU
{9F21C208-B1B7-4203-8034-79493F06BA1C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -359,5 +373,6 @@ Global
{59CC0FFE-92F4-E0A7-1AC9-3F1F42E6F62C} = {B36A84DF-456D-A817-6EDD-3EC3E7F6E11F}
{E9101478-ACF8-4253-96B7-2D3070BC3F23} = {59CC0FFE-92F4-E0A7-1AC9-3F1F42E6F62C}
{60CD65BA-8A92-41D5-BB1A-0D39AA42184B} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{9F21C208-B1B7-4203-8034-79493F06BA1C} = {0AB3BF05-4346-4AA6-1389-037BE0695223}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using GenWave.Core.Abstractions;

namespace GenWave.Orchestration;
Expand Down Expand Up @@ -118,6 +119,44 @@ public static IServiceCollection AddGenWaveOrchestration(this IServiceCollection
// constructor parameter's own remarks).
services.AddSingleton<ClockAnchoredImagingProducer>();

return services.AddSingleton<INextItemProvider, Orchestrator>();
// The production construction site (SPEC F184.3/F184.5, STORY-451, T514). The only other
// `new Orchestrator(` is GenWave.TestSupport's OrchestratorBuilder; Story451_ConstructionPins
// pins the pair. A factory, not AddSingleton<INextItemProvider, Orchestrator>(): every seam is
// read INSIDE the lambda, i.e. at first resolve of INextItemProvider, never at this call.
// That is load-bearing β€” Program.cs registers IPersonaPickProvider, IRequestFulfillmentSource,
// IStationEventSink, IContextSettingsProvider and IAdSpotVend AFTER AddGenWaveOrchestration so
// they beat the TryAdd defaults above; a resolve-time read sees the last registration, exactly
// as constructor injection did. Optional seams with a NoOp type coalesce to it; the other ten
// pass GetService's null through to the Orchestrator's own null handling, unchanged.
services.AddSingleton<INextItemProvider>(sp => new Orchestrator(
sp.GetRequiredService<IStationIdentityProvider>(),
sp.GetRequiredService<IStationScopeProvider>(),
sp.GetRequiredService<ICadenceProvider>(),
sp.GetRequiredService<IRotationSettingsProvider>(),
sp.GetRequiredService<MusicSelectionPolicy>(),
sp.GetRequiredService<ITtsSegmentSource>(),
sp.GetRequiredService<IActivePersonaAccessor>(),
sp.GetRequiredService<ILogger<Orchestrator>>(),
sp.GetRequiredService<IRenderBudgetProvider>(),
sp.GetRequiredService<SpeechDeferralQueue>(),
sp.GetRequiredService<TimeProvider>(),
sp.GetRequiredService<IBoundaryBiasProvider>(),
scheduleResolver: sp.GetService<CachingScheduleResolver>(),
personaStore: sp.GetService<IPersonaStore>(),
events: sp.GetService<IStationEventSink>() ?? NoOpStationEventSink.Instance,
stationClock: sp.GetService<IStationClockProvider>(),
patterEstimator: sp.GetService<IPatterDurationEstimator>(),
contextSettings: sp.GetService<IContextSettingsProvider>() ?? NoOpContextSettingsProvider.Instance,
catalog: sp.GetService<IMediaCatalog>(),
imagingSettings: sp.GetService<IStationImagingSettingsProvider>() ?? NoOpStationImagingSettingsProvider.Instance,
crosstalkPlanner: sp.GetService<CrosstalkPlanner>(),
announcementSource: sp.GetService<IAnnouncementSource>(),
announcementRenderer: sp.GetService<IVerbatimSegmentRenderer>(),
voiceLister: sp.GetService<ITtsVoiceLister>(),
announcementCopyWriter: sp.GetService<IAnnouncementCopyWriter>(),
adCadenceProvider: sp.GetService<IAdCadenceProvider>() ?? NoOpAdCadenceProvider.Instance,
adSpotVend: sp.GetService<IAdSpotVend>() ?? NoOpAdSpotVend.Instance));

return services;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@

<ItemGroup>
<ProjectReference Include="..\..\src\GenWave.Ads\GenWave.Ads.csproj" />
<ProjectReference Include="..\GenWave.TestSupport\GenWave.TestSupport.csproj" />
</ItemGroup>

</Project>
106 changes: 106 additions & 0 deletions tests/GenWave.Architecture.Tests/Specs/Story451_ConstructionPins.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// STORY-451 β€” One construction path β€” the pins (gh-#401 Β· SPEC F184.3–F184.5 Β· PLAN T510, T513, T514)
//
// BDD specification β€” xUnit. AC4 scans src/ and tests/ for the Orchestrator's own construction call;
// AC5 reads Host.Tests' csproj and file list; AC8 reflects the TestSupport assembly for [Fact] methods.
//
// GREEN at T514: T513 already un-skipped ScenarioHostTestsAfterTheMove's three facts.
// ScenarioTheTextScanForConstruction's three facts land here, now that AddGenWaveOrchestration's own
// factory registration is the second (and last) construction site alongside OrchestratorBuilder.

using System.Reflection;
using System.Xml.Linq;
using GenWave.Architecture.Tests.Support;

namespace GenWave.Architecture.Tests.Specs;

public static class FeatureConstructionPins
{
// ---------------------------------------------------------------------
// HAPPY PATH
// ---------------------------------------------------------------------

public sealed class ScenarioTheTextScanForConstruction
{
// Given: every .cs under src/ and tests/ (bin/obj excluded) scanned for the literal below β€”
// split, mirroring Story442_FakeClockPins.cs's own hand-rolled-clock-declaration split, so
// this file's own source text never spells the literal out contiguously and self-matches
// the scan (and never spells THAT pin's own literal out contiguously either).
const string ConstructorCallLiteral = "new " + "Orchestrator(";

static readonly string[] hits = new[] { "src", "tests" }
.SelectMany(dir => Directory.EnumerateFiles(
Path.Combine(SolutionLocator.Root(), dir), "*.cs", SearchOption.AllDirectories))
.Where(path => !path.Split('/', '\\').Any(segment => segment is "bin" or "obj"))
.Where(path => File.ReadAllText(path).Contains(ConstructorCallLiteral, StringComparison.Ordinal))
.ToArray();

/// <summary>AC4 β€” the builder is one site</summary>
[Fact]
public void HitsOrchestratorBuilder() =>
Assert.Contains(hits, path => path.EndsWith("OrchestratorBuilder.cs", StringComparison.Ordinal));

/// <summary>AC4 β€” AddOrchestration is the other site</summary>
[Fact]
public void HitsTheServiceCollectionExtensions() =>
Assert.Contains(hits, path => path.EndsWith("OrchestrationServiceCollectionExtensions.cs", StringComparison.Ordinal));

/// <summary>AC4 β€” exactly two files hit</summary>
[Fact]
public void HitsNothingElse() => Assert.Equal(2, hits.Length);
}

public sealed class ScenarioHostTestsAfterTheMove
{
// Given: GenWave.Host.Tests.csproj and its file tree

static readonly string HostTestsDir =
Path.Combine(SolutionLocator.Root(), "tests", "GenWave.Host.Tests");

/// <summary>AC5 β€” the ProjectReference is present</summary>
[Fact]
public void ReferencesTestSupport()
{
var csprojPath = Path.Combine(HostTestsDir, "GenWave.Host.Tests.csproj");
var csproj = XDocument.Load(csprojPath);

var referencesTestSupport = csproj.Descendants("ProjectReference")
.Select(reference => reference.Attribute("Include")?.Value)
.Any(include => include is not null
&& include.EndsWith("GenWave.TestSupport.csproj", StringComparison.Ordinal));

Assert.True(referencesTestSupport, $"{csprojPath} carries no ProjectReference to GenWave.TestSupport");
}

/// <summary>AC5 β€” the duplicate fake is deleted</summary>
[Fact]
public void NoLongerCarriesFakeRenderBudgetProvider() =>
Assert.Empty(Directory.EnumerateFiles(HostTestsDir, "FakeRenderBudgetProvider.cs", SearchOption.AllDirectories));

/// <summary>AC5 β€” the duplicate fake is deleted</summary>
[Fact]
public void NoLongerCarriesFakeBoundaryBiasProvider() =>
Assert.Empty(Directory.EnumerateFiles(HostTestsDir, "FakeBoundaryBiasProvider.cs", SearchOption.AllDirectories));
}

public sealed class ScenarioTheSupportAssembly
{
// Given: GenWave.TestSupport reflected

/// <summary>AC8 β€” the project carries fakes, not facts</summary>
[Fact]
public void HasNoFactMethods()
{
var assembly = typeof(GenWave.TestSupport.AssemblyMarker).Assembly;

var factMethods = assembly.GetTypes()
.SelectMany(type => type.GetMethods(
BindingFlags.Public | BindingFlags.NonPublic
| BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
.Where(method => method.GetCustomAttributes(inherit: true)
.Any(attribute => attribute is Xunit.FactAttribute))
.ToList();

Assert.Empty(factMethods);
}
}
}
34 changes: 34 additions & 0 deletions tests/GenWave.Architecture.Tests/Specs/Story456_SeamIndex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// STORY-456 β€” The speaker travels with the plan β€” the seam index (gh-#772 Β· SPEC F189.7 Β· PLAN T528)
//
// BDD specification β€” xUnit. AC11 runs tools/SeamIndexGenerator in check mode and reads the committed SEAMS.md.
//
// RED at plan time: every fact is [Fact(Skip = Pending)] with a loud body β€” remove the Skip only in the
// task that makes it green. The builder comments name the arrange each scenario needs.

namespace GenWave.Architecture.Tests.Specs;

public static class FeatureSeamIndexListsTheSpeakerSource
{
const string Pending = "pending: T528 β€” SEAMS.md regenerated with ISpeakerSnapshotSource and IPersonaCardByIdSource (STORY-456)";

// ---------------------------------------------------------------------
// HAPPY PATH
// ---------------------------------------------------------------------

public sealed class ScenarioTheCommittedSeamIndex
{
// Given: SEAMS.md at the repo root

/// <summary>AC11 β€” </summary>
[Fact(Skip = Pending)]
public void ListsISpeakerSnapshotSource() => Assert.Fail(Pending);

/// <summary>AC11 β€” </summary>
[Fact(Skip = Pending)]
public void ListsIPersonaCardByIdSource() => Assert.Fail(Pending);

/// <summary>AC11 β€” </summary>
[Fact(Skip = Pending)]
public void IsByteIdenticalToTheGenerator() => Assert.Fail(Pending);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// STORY-460 β€” The Orchestrator after (gh-#401 Β· SPEC F193 Β· PLAN T537)
//
// BDD specification β€” xUnit. AC1 reflects every public constructor in GenWave.Orchestration; AC2 is a text scan; AC3 reflects the Orchestrator;
// AC4 reads the replay's ceremony-only trace; AC5 is the existing laws.
//
// RED at plan time: every fact is [Fact(Skip = Pending)] with a loud body β€” remove the Skip only in the
// task that makes it green. The builder comments name the arrange each scenario needs.

namespace GenWave.Architecture.Tests.Specs;

public static class FeatureTheOrchestratorAfter
{
const string Pending = "pending: T537 β€” fitness pins: no optional seam params, two construction sites, the public surface (STORY-460)";

// ---------------------------------------------------------------------
// HAPPY PATH
// ---------------------------------------------------------------------

public sealed class ScenarioEveryPublicConstructorInOrchestration
{
// Given: parameters reflected

/// <summary>AC1 β€” </summary>
[Fact(Skip = Pending)]
public void NoInterfaceParameterHasADefault() => Assert.Fail(Pending);
}

public sealed class ScenarioTheTextScanAfterTheSplit
{
// Given: the Orchestrator's own construction call, scanned over src/ and tests/

/// <summary>AC2 β€” </summary>
[Fact(Skip = Pending)]
public void HitsExactlyTheBuilderAndTheRoot() => Assert.Fail(Pending);
}

public sealed class ScenarioTheOrchestratorReflected
{
// Given: typeof(Orchestrator)

/// <summary>AC3 β€” </summary>
[Fact(Skip = Pending)]
public void ImplementsINextItemProvider() => Assert.Fail(Pending);

/// <summary>AC3 β€” </summary>
[Fact(Skip = Pending)]
public void ImplementsIBoundaryFitLog() => Assert.Fail(Pending);

/// <summary>AC3 β€” </summary>
[Fact(Skip = Pending)]
public void ExposesSignOffLeadTime() => Assert.Fail(Pending);

/// <summary>AC3 β€” </summary>
[Fact(Skip = Pending)]
public void ExposesTimeDateHonestyThreshold() => Assert.Fail(Pending);
}

public sealed class ScenarioTheCeremonyOnlyUnitsTrace
{
// Given: the replay's ceremony-only unit

/// <summary>AC4 β€” </summary>
[Fact(Skip = Pending)]
public void ListsTheSignOff() => Assert.Fail(Pending);

/// <summary>AC4 β€” </summary>
[Fact(Skip = Pending)]
public void ListsNoLeadIn() => Assert.Fail(Pending);
}

public sealed class ScenarioTheLaws
{
// Given: L1, L5, L10, L11

/// <summary>AC5 β€” </summary>
[Fact(Skip = Pending)]
public void AllGreen() => Assert.Fail(Pending);
}
}
16 changes: 0 additions & 16 deletions tests/GenWave.Host.Tests/FakeRenderBudgetProvider.cs

This file was deleted.

4 changes: 4 additions & 0 deletions tests/GenWave.Host.Tests/GenWave.Host.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@
<None Include="Fixtures\abstractions-5.7.0-surface.txt" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\GenWave.TestSupport\GenWave.TestSupport.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using GenWave.Host.Tests.Fakes;
using GenWave.Host.Tests.Support;
using GenWave.Orchestration;
using GenWave.TestSupport;
using GenWave.Tts;

namespace GenWave.Host.Tests.Specs;
Expand Down Expand Up @@ -184,12 +185,21 @@ static Orchestrator BuildOrchestrator(
var rotationProvider = new FakeRotationSettingsProvider(new RotationSettings());

var musicSelectionPolicy = new MusicSelectionPolicy(catalog, NullLogger<MusicSelectionPolicy>.Instance);
return new Orchestrator(
identityProvider, scopeProvider, cadenceProvider, rotationProvider, musicSelectionPolicy, segmentSource,
new NoOpActivePersonaAccessor(), NullLogger<Orchestrator>.Instance,
new FakeRenderBudgetProvider(renderBudget),
new SpeechDeferralQueue(TimeProvider.System),
TimeProvider.System, new FakeBoundaryBiasProvider(TimeSpan.Zero));
return new OrchestratorBuilder()
.WithIdentity(identityProvider)
.WithScope(scopeProvider)
.WithCadence(cadenceProvider)
.WithRotation(rotationProvider)
.WithMusicSelectionPolicy(musicSelectionPolicy)
.WithTts(segmentSource)
.WithPersonaAccessor(new NoOpActivePersonaAccessor())
.WithLogger(NullLogger<Orchestrator>.Instance)
.WithRenderBudget(renderBudget)
.WithDeferralQueue(new SpeechDeferralQueue(TimeProvider.System))
.WithTime(TimeProvider.System)
.WithLookahead(TimeSpan.Zero)
.Build()
.Orchestrator;
}

// -------------------------------------------------------------------------
Expand Down
Loading
Loading