feat(datasync): add ScheduleConfig and ScheduleDefaults models

Add configuration models for the new schedule-based pipeline configuration:
- ScheduleConfig: Per-schedule configuration (Enabled, IntervalMinutes, PrePurge, ReIndex, UpdateWhen)
- ScheduleDefaults: Default configurations for Mass (weekly), Daily, and Hourly schedules
- PipelineSchedules: Per-pipeline schedule overrides
- MergeWith method for merging pipeline overrides with defaults

Part of Task 1 from pipeline-schedule-alignment implementation plan.
This commit is contained in:
Joseph Doherty
2026-01-07 00:18:28 -05:00
parent bd8cb87275
commit 1318dce18a
2 changed files with 274 additions and 0 deletions
@@ -0,0 +1,98 @@
namespace JdeScoping.DataSync.Configuration;
/// <summary>
/// Configuration for a single schedule type (Mass/Daily/Hourly).
/// </summary>
public record ScheduleConfig
{
/// <summary>
/// Whether this schedule is enabled.
/// </summary>
public bool Enabled { get; init; } = true;
/// <summary>
/// Interval in minutes between syncs.
/// </summary>
public int IntervalMinutes { get; init; }
/// <summary>
/// Whether to truncate the table before import (full reload).
/// </summary>
public bool PrePurge { get; init; }
/// <summary>
/// Whether to rebuild indexes after import.
/// </summary>
public bool ReIndex { get; init; }
/// <summary>
/// Condition for updating existing rows (e.g., "src.LastUpdateDt > tgt.LastUpdateDt").
/// </summary>
public string? UpdateWhen { get; init; }
/// <summary>
/// Merges this config with defaults. Non-null/non-default values in this config override defaults.
/// </summary>
public ScheduleConfig MergeWith(ScheduleConfig defaults)
{
return new ScheduleConfig
{
Enabled = Enabled,
IntervalMinutes = IntervalMinutes > 0 ? IntervalMinutes : defaults.IntervalMinutes,
PrePurge = PrePurge || defaults.PrePurge,
ReIndex = ReIndex || defaults.ReIndex,
UpdateWhen = UpdateWhen ?? defaults.UpdateWhen
};
}
}
/// <summary>
/// Default schedule configurations for all pipelines.
/// </summary>
public record ScheduleDefaults
{
/// <summary>
/// Default Mass schedule config (weekly, full reload).
/// </summary>
public ScheduleConfig Mass { get; init; } = new()
{
Enabled = true,
IntervalMinutes = 10080, // Weekly
PrePurge = true,
ReIndex = true
};
/// <summary>
/// Default Daily schedule config (incremental merge).
/// </summary>
public ScheduleConfig Daily { get; init; } = new()
{
Enabled = true,
IntervalMinutes = 1440, // Daily
PrePurge = false,
ReIndex = false,
UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
};
/// <summary>
/// Default Hourly schedule config (incremental merge).
/// </summary>
public ScheduleConfig Hourly { get; init; } = new()
{
Enabled = true,
IntervalMinutes = 60, // Hourly
PrePurge = false,
ReIndex = false,
UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
};
}
/// <summary>
/// Per-pipeline schedule overrides.
/// </summary>
public record PipelineSchedules
{
public ScheduleConfig? Mass { get; init; }
public ScheduleConfig? Daily { get; init; }
public ScheduleConfig? Hourly { get; init; }
}
@@ -0,0 +1,176 @@
using JdeScoping.DataSync.Configuration;
using Shouldly;
namespace JdeScoping.DataSync.Tests.Configuration;
public class ScheduleConfigTests
{
[Fact]
public void ScheduleConfig_DefaultValues_AreCorrect()
{
var config = new ScheduleConfig();
config.Enabled.ShouldBeTrue();
config.IntervalMinutes.ShouldBe(0);
config.PrePurge.ShouldBeFalse();
config.ReIndex.ShouldBeFalse();
config.UpdateWhen.ShouldBeNull();
}
[Fact]
public void ScheduleConfig_WithValues_StoresCorrectly()
{
var config = new ScheduleConfig
{
Enabled = false,
IntervalMinutes = 60,
PrePurge = true,
ReIndex = true,
UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
};
config.Enabled.ShouldBeFalse();
config.IntervalMinutes.ShouldBe(60);
config.PrePurge.ShouldBeTrue();
config.ReIndex.ShouldBeTrue();
config.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
}
[Fact]
public void ScheduleDefaults_HasCorrectDefaultValues()
{
var defaults = new ScheduleDefaults();
defaults.Mass.ShouldNotBeNull();
defaults.Daily.ShouldNotBeNull();
defaults.Hourly.ShouldNotBeNull();
}
[Fact]
public void ScheduleDefaults_Mass_HasCorrectValues()
{
var defaults = new ScheduleDefaults();
defaults.Mass.Enabled.ShouldBeTrue();
defaults.Mass.IntervalMinutes.ShouldBe(10080); // Weekly
defaults.Mass.PrePurge.ShouldBeTrue();
defaults.Mass.ReIndex.ShouldBeTrue();
defaults.Mass.UpdateWhen.ShouldBeNull();
}
[Fact]
public void ScheduleDefaults_Daily_HasCorrectValues()
{
var defaults = new ScheduleDefaults();
defaults.Daily.Enabled.ShouldBeTrue();
defaults.Daily.IntervalMinutes.ShouldBe(1440); // Daily
defaults.Daily.PrePurge.ShouldBeFalse();
defaults.Daily.ReIndex.ShouldBeFalse();
defaults.Daily.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
}
[Fact]
public void ScheduleDefaults_Hourly_HasCorrectValues()
{
var defaults = new ScheduleDefaults();
defaults.Hourly.Enabled.ShouldBeTrue();
defaults.Hourly.IntervalMinutes.ShouldBe(60); // Hourly
defaults.Hourly.PrePurge.ShouldBeFalse();
defaults.Hourly.ReIndex.ShouldBeFalse();
defaults.Hourly.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
}
[Fact]
public void PipelineSchedules_AllPropertiesNullable()
{
var schedules = new PipelineSchedules();
schedules.Mass.ShouldBeNull();
schedules.Daily.ShouldBeNull();
schedules.Hourly.ShouldBeNull();
}
[Fact]
public void PipelineSchedules_WithValues_StoresCorrectly()
{
var schedules = new PipelineSchedules
{
Mass = new ScheduleConfig { PrePurge = true },
Daily = new ScheduleConfig { Enabled = true },
Hourly = new ScheduleConfig { Enabled = false }
};
schedules.Mass.ShouldNotBeNull();
schedules.Mass!.PrePurge.ShouldBeTrue();
schedules.Daily.ShouldNotBeNull();
schedules.Daily!.Enabled.ShouldBeTrue();
schedules.Hourly.ShouldNotBeNull();
schedules.Hourly!.Enabled.ShouldBeFalse();
}
[Fact]
public void MergeWith_WhenConfigHasNoOverrides_ReturnsDefaultValues()
{
var config = new ScheduleConfig();
var defaults = new ScheduleConfig
{
Enabled = true,
IntervalMinutes = 60,
PrePurge = false,
ReIndex = false,
UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
};
var merged = config.MergeWith(defaults);
merged.IntervalMinutes.ShouldBe(60);
merged.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
}
[Fact]
public void MergeWith_WhenConfigHasOverrides_UsesOverrideValues()
{
var config = new ScheduleConfig
{
IntervalMinutes = 120,
PrePurge = true,
UpdateWhen = "custom condition"
};
var defaults = new ScheduleConfig
{
IntervalMinutes = 60,
PrePurge = false,
UpdateWhen = "default condition"
};
var merged = config.MergeWith(defaults);
merged.IntervalMinutes.ShouldBe(120);
merged.PrePurge.ShouldBeTrue();
merged.UpdateWhen.ShouldBe("custom condition");
}
[Fact]
public void MergeWith_PreservesEnabledFromConfig()
{
var config = new ScheduleConfig { Enabled = false };
var defaults = new ScheduleConfig { Enabled = true };
var merged = config.MergeWith(defaults);
merged.Enabled.ShouldBeFalse();
}
[Fact]
public void MergeWith_WhenIntervalZero_UsesDefaultInterval()
{
var config = new ScheduleConfig { IntervalMinutes = 0 };
var defaults = new ScheduleConfig { IntervalMinutes = 1440 };
var merged = config.MergeWith(defaults);
merged.IntervalMinutes.ShouldBe(1440);
}
}