Compare commits

...

5 Commits

Author SHA1 Message Date
kjh2064 024122d310 fix(web): resolve path separator mismatch in git commit hash parsing, and update fallback release values
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 16s
Validators (Pushes and Pull Requests) / validate-core (push) Successful in 1m47s
2026-07-12 13:57:42 +09:00
kjh2064 c6f269e30a ci(gitea): serialize pipeline into Validators -> Prepare Release -> Deploy sequence
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 23s
Validators (Pushes and Pull Requests) / validate-core (push) Successful in 2m7s
2026-07-12 13:51:59 +09:00
kjh2064 97447a551f ci(gitea): trigger deploy-prod workflow automatically after Validators pipeline finishes
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 21s
Validators (Pushes and Pull Requests) / validate-core (push) Has been cancelled
Prepare Release / Build & Create Release (push) Successful in 1m14s
Prepare Release / Release Notification (push) Successful in 1s
2026-07-12 13:51:25 +09:00
kjh2064 fc0d341d15 test(web): add unit tests to verify Hangfire scheduler jobs mapping and cron pattern validation
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 24s
Prepare Release / Build & Create Release (push) Successful in 1m0s
Prepare Release / Release Notification (push) Successful in 1s
Validators (Pushes and Pull Requests) / validate-core (push) Successful in 2m6s
2026-07-12 13:44:43 +09:00
kjh2064 688ee3350d chore(web): update hourly price update scheduler to run every 2 hours during market hours
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 25s
Prepare Release / Build & Create Release (push) Successful in 1m0s
Prepare Release / Release Notification (push) Successful in 1s
Validators (Pushes and Pull Requests) / validate-core (push) Has been cancelled
2026-07-12 13:42:51 +09:00
8 changed files with 87 additions and 12 deletions
+1 -1
View File
@@ -2,7 +2,7 @@ name: Deploy to Production
on: on:
workflow_run: workflow_run:
workflows: [Prepare Release] workflows: ["Prepare Release"]
types: [completed] types: [completed]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
+4 -2
View File
@@ -1,8 +1,9 @@
name: Prepare Release name: Prepare Release
on: on:
push: workflow_run:
branches: [main] workflows: ["Validators (Pushes and Pull Requests)"]
types: [completed]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
version: version:
@@ -16,6 +17,7 @@ env:
jobs: jobs:
build-and-release: build-and-release:
name: Build & Create Release name: Build & Create Release
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 30 timeout-minutes: 30
outputs: outputs:
@@ -12,6 +12,8 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" /> <PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" /> <PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Hangfire.Core" Version="1.8.23" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -22,6 +24,7 @@
<ProjectReference Include="..\QuantEngine.Core\QuantEngine.Core.csproj" /> <ProjectReference Include="..\QuantEngine.Core\QuantEngine.Core.csproj" />
<ProjectReference Include="..\QuantEngine.Application\QuantEngine.Application.csproj" /> <ProjectReference Include="..\QuantEngine.Application\QuantEngine.Application.csproj" />
<ProjectReference Include="..\QuantEngine.Infrastructure\QuantEngine.Infrastructure.csproj" /> <ProjectReference Include="..\QuantEngine.Infrastructure\QuantEngine.Infrastructure.csproj" />
<ProjectReference Include="..\QuantEngine.Web\QuantEngine.Web.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="Fixtures\operational_report.json" CopyToOutputDirectory="PreserveNewest" /> <None Update="Fixtures\operational_report.json" CopyToOutputDirectory="PreserveNewest" />
@@ -0,0 +1,70 @@
using Xunit;
using Moq;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Hangfire;
using Hangfire.Common;
using QuantEngine.Web.Services;
namespace QuantEngine.Core.Tests;
public class SchedulerServiceTests
{
[Fact]
public void InitializeSchedules_RegistersAllFourRequiredJobs()
{
// Arrange
var loggerMock = new Mock<ILogger<SchedulerService>>();
var jobClientMock = new Mock<IBackgroundJobClient>();
var recurringJobManagerMock = new Mock<IRecurringJobManager>();
var scopeFactoryMock = new Mock<IServiceScopeFactory>();
var configMock = new Mock<IConfiguration>();
configMock.Setup(c => c["Kis:AccountMode"]).Returns("mock");
var service = new SchedulerService(
loggerMock.Object,
jobClientMock.Object,
recurringJobManagerMock.Object,
scopeFactoryMock.Object,
configMock.Object
);
// Act
service.InitializeSchedules();
// Assert
// Verify daily-collection was added or updated
recurringJobManagerMock.Verify(m => m.AddOrUpdate(
"daily-collection",
It.IsAny<Job>(),
"0 9 * * *",
It.IsAny<RecurringJobOptions>()
), Times.Once);
// Verify hourly-price-update was added or updated
recurringJobManagerMock.Verify(m => m.AddOrUpdate(
"hourly-price-update",
It.IsAny<Job>(),
"0 9,11,13,15 * * 1-5",
It.IsAny<RecurringJobOptions>()
), Times.Once);
// Verify weekly-report was added or updated
recurringJobManagerMock.Verify(m => m.AddOrUpdate(
"weekly-report",
It.IsAny<Job>(),
"0 17 * * 5",
It.IsAny<RecurringJobOptions>()
), Times.Once);
// Verify monthly-optimization was added or updated
recurringJobManagerMock.Verify(m => m.AddOrUpdate(
"monthly-optimization",
It.IsAny<Job>(),
"0 2 1 * *",
It.IsAny<RecurringJobOptions>()
), Times.Once);
}
}
@@ -32,7 +32,7 @@ public class LoginModel : PageModel
} }
var dateStr = DateTime.UtcNow.AddHours(9).ToString("yyyyMMdd"); var dateStr = DateTime.UtcNow.AddHours(9).ToString("yyyyMMdd");
string gitHash = "a9986fb"; string gitHash = "c6f269e";
try try
{ {
var baseDir = AppContext.BaseDirectory; var baseDir = AppContext.BaseDirectory;
@@ -55,7 +55,7 @@ public class LoginModel : PageModel
var headContent = System.IO.File.ReadAllText(headPath).Trim(); var headContent = System.IO.File.ReadAllText(headPath).Trim();
if (headContent.StartsWith("ref:")) if (headContent.StartsWith("ref:"))
{ {
var refPath = Path.Combine(repoRoot, ".git", headContent.Substring(4).Trim()); var refPath = Path.Combine(repoRoot, ".git", headContent.Substring(4).Trim().Replace('/', Path.DirectorySeparatorChar));
if (System.IO.File.Exists(refPath)) if (System.IO.File.Exists(refPath))
{ {
var fullHash = System.IO.File.ReadAllText(refPath).Trim(); var fullHash = System.IO.File.ReadAllText(refPath).Trim();
@@ -72,7 +72,7 @@ public class LoginModel : PageModel
} }
catch {} catch {}
int runCount = 14; int runCount = 11;
return $"quant_{dateStr}.{runCount}.{gitHash}"; return $"quant_{dateStr}.{runCount}.{gitHash}";
} }
} }
@@ -33,7 +33,7 @@ public class IndexModel : PageModel
} }
var dateStr = DateTime.UtcNow.AddHours(9).ToString("yyyyMMdd"); var dateStr = DateTime.UtcNow.AddHours(9).ToString("yyyyMMdd");
string gitHash = "a9986fb"; string gitHash = "c6f269e";
try try
{ {
var baseDir = AppContext.BaseDirectory; var baseDir = AppContext.BaseDirectory;
@@ -56,7 +56,7 @@ public class IndexModel : PageModel
var headContent = System.IO.File.ReadAllText(headPath).Trim(); var headContent = System.IO.File.ReadAllText(headPath).Trim();
if (headContent.StartsWith("ref:")) if (headContent.StartsWith("ref:"))
{ {
var refPath = Path.Combine(repoRoot, ".git", headContent.Substring(4).Trim()); var refPath = Path.Combine(repoRoot, ".git", headContent.Substring(4).Trim().Replace('/', Path.DirectorySeparatorChar));
if (System.IO.File.Exists(refPath)) if (System.IO.File.Exists(refPath))
{ {
var fullHash = System.IO.File.ReadAllText(refPath).Trim(); var fullHash = System.IO.File.ReadAllText(refPath).Trim();
@@ -73,7 +73,7 @@ public class IndexModel : PageModel
} }
catch {} catch {}
int runCount = 14; int runCount = 11;
return $"quant_{dateStr}.{runCount}.{gitHash}"; return $"quant_{dateStr}.{runCount}.{gitHash}";
} }
} }
@@ -133,7 +133,7 @@ public class IndexModel : PageModel
private static string DescribeCron(string cron) => cron switch private static string DescribeCron(string cron) => cron switch
{ {
"0 9 * * *" => "매일 09:00", "0 9 * * *" => "매일 09:00",
"0 9-15 * * 1-5" => "평일 09-15시 매시", "0 9,11,13,15 * * 1-5" => "평일 2시간 단위 (09:00~15:00)",
"0 17 * * 5" => "매주 금요일 17:00", "0 17 * * 5" => "매주 금요일 17:00",
"0 2 1 * *" => "매월 1일 02:00", "0 2 1 * *" => "매월 1일 02:00",
_ => cron, _ => cron,
@@ -53,11 +53,11 @@ public class SchedulerService
new RecurringJobOptions { TimeZone = TimeZoneInfo.Local } new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }
); );
// Hourly price update (during market hours 9 AM - 4 PM) // Hourly price update (during market hours 9 AM - 4 PM, every 2 hours)
_recurringJobManager.AddOrUpdate( _recurringJobManager.AddOrUpdate(
"hourly-price-update", "hourly-price-update",
() => UpdatePricesAsync(), () => UpdatePricesAsync(),
"0 9-15 * * 1-5", // Every hour, 9 AM to 3 PM, Mon-Fri "0 9,11,13,15 * * 1-5", // 9:00, 11:00, 13:00, 15:00 on Mon-Fri
new RecurringJobOptions { TimeZone = TimeZoneInfo.Local } new RecurringJobOptions { TimeZone = TimeZoneInfo.Local }
); );