NEO-147: guard DenyCreateFailure when create snapshot is null
parent
81f4cde673
commit
c237bead53
|
|
@ -70,7 +70,7 @@ Issue a validated **`active`** contract instance from **`ContractSeed`** inputs
|
|||
|
||||
- **Types:** `ContractGeneratorReasonCodes`, `ContractIssueOperationResult`, `ContractInstanceIds.MakeDeterministicInstanceId`.
|
||||
- **Orchestrator:** `ContractGeneratorOperations.TryIssue` — band + faction template selection, deterministic id, store persist.
|
||||
- **Tests:** `ContractGeneratorOperationsTests` (13 unit), `ContractGeneratorOperationsIntegrationTests` (DI), `ContractGeneratorPersistenceIntegrationTests` (`RequirePostgresFact`); **890** tests green.
|
||||
- **Tests:** `ContractGeneratorOperationsTests` (14 unit), `ContractGeneratorOperationsIntegrationTests` (DI), `ContractGeneratorPersistenceIntegrationTests` (`RequirePostgresFact`); **891** tests green.
|
||||
- **Docs:** `server/README.md` contract generator section; `E7_M4_ContractMissionGenerator.md` NEO-147 note.
|
||||
|
||||
## Technical approach
|
||||
|
|
|
|||
|
|
@ -195,6 +195,30 @@ public sealed class ContractGeneratorOperationsTests
|
|||
Assert.False(deps.InstanceStore.TryGetActiveForPlayer(PlayerId, out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryIssue_ShouldDenyInvalidIds_WhenCreateActiveReturnsFalseWithoutSnapshot()
|
||||
{
|
||||
// Arrange
|
||||
var deps = CreateDependencies();
|
||||
var store = new CreateActiveNullSnapshotStore(deps.InstanceStore);
|
||||
var templateRegistry = deps.TemplateRegistry;
|
||||
var standingStore = deps.StandingStore;
|
||||
// Act
|
||||
var result = ContractGeneratorOperations.TryIssue(
|
||||
PlayerId,
|
||||
TemplateId,
|
||||
SeedBucket,
|
||||
zoneDifficultyBand: null,
|
||||
templateRegistry,
|
||||
store,
|
||||
standingStore,
|
||||
deps.TimeProvider);
|
||||
// Assert
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(ContractGeneratorReasonCodes.InvalidIds, result.ReasonCode);
|
||||
Assert.Null(result.Snapshot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryIssue_ShouldDenyPlayerNotWritable_WhenPlayerUnknown()
|
||||
{
|
||||
|
|
@ -290,4 +314,38 @@ public sealed class ContractGeneratorOperationsTests
|
|||
IContractInstanceStore InstanceStore,
|
||||
IFactionStandingStore StandingStore,
|
||||
TimeProvider TimeProvider);
|
||||
|
||||
/// <summary>Simulates store create failure without populating the out snapshot (Bugbot regression).</summary>
|
||||
private sealed class CreateActiveNullSnapshotStore(IContractInstanceStore inner) : IContractInstanceStore
|
||||
{
|
||||
public bool CanWritePlayer(string playerId) => inner.CanWritePlayer(playerId);
|
||||
|
||||
public bool TryGet(string playerId, string contractInstanceId, out ContractInstanceState snapshot) =>
|
||||
inner.TryGet(playerId, contractInstanceId, out snapshot);
|
||||
|
||||
public bool TryGetActiveForPlayer(string playerId, out ContractInstanceState snapshot) =>
|
||||
inner.TryGetActiveForPlayer(playerId, out snapshot);
|
||||
|
||||
public bool TryCreateActive(
|
||||
string playerId,
|
||||
string contractInstanceId,
|
||||
string templateId,
|
||||
string seedBucket,
|
||||
DateTimeOffset issuedAt,
|
||||
out ContractInstanceState snapshot)
|
||||
{
|
||||
snapshot = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryMarkComplete(
|
||||
string playerId,
|
||||
string contractInstanceId,
|
||||
DateTimeOffset completedAt,
|
||||
out ContractInstanceState snapshot) =>
|
||||
inner.TryMarkComplete(playerId, contractInstanceId, completedAt, out snapshot);
|
||||
|
||||
public bool TryClearInstance(string playerId, string contractInstanceId) =>
|
||||
inner.TryClearInstance(playerId, contractInstanceId);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,14 +142,14 @@ public static class ContractGeneratorOperations
|
|||
private static ContractIssueOperationResult DenyCreateFailure(
|
||||
IContractInstanceStore instanceStore,
|
||||
string normalizedPlayerId,
|
||||
ContractInstanceState attemptSnapshot)
|
||||
ContractInstanceState? attemptSnapshot)
|
||||
{
|
||||
if (instanceStore.TryGetActiveForPlayer(normalizedPlayerId, out var active))
|
||||
{
|
||||
return Deny(ContractGeneratorReasonCodes.ActiveContractExists, active);
|
||||
}
|
||||
|
||||
if (attemptSnapshot.Status == ContractInstanceStatus.Active)
|
||||
if (attemptSnapshot is { Status: ContractInstanceStatus.Active })
|
||||
{
|
||||
return Deny(ContractGeneratorReasonCodes.ActiveContractExists, attemptSnapshot);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue