backend: serve single-agent task templates with their status and results, rename the kinds in storage

This commit is contained in:
justme committed 2026-10-22 07:02:28 +08:00
1 parent e77880d372
commit 3a9ea46d6f
45 files changed
+763 -216

No files matched your search

@@ -48,8 +48,8 @@ public sealed class StorageTests : IDisposable
public async Task Raw_AppendReadAndDedupe()
{
var raw = new SqliteRawStore(_db);
RawChunkAppend a = Chunk(s_agent, "udp_echo", 1_000, "2026-01-01");
RawChunkAppend b = Chunk(s_agent, "udp_echo", 11_000, "2026-01-01");
RawChunkAppend a = Chunk(s_agent, "quic_ping", 1_000, "2026-01-01");
RawChunkAppend b = Chunk(s_agent, "quic_ping", 11_000, "2026-01-01");
Assert.True(await raw.AppendAsync(a, default));
Assert.True(await raw.AppendAsync(b, default));
@@ -57,7 +57,7 @@ public sealed class StorageTests : IDisposable
IReadOnlyList<RawChunkMeta> metas = await raw.FindAsync(new RawQuery(s_agent), default);
Assert.Equal(2, metas.Count);
Assert.Equal("2026-01-01/agt_storage/udp_echo.seg", metas[0].SegmentPath);
Assert.Equal("2026-01-01/agt_storage/quic_ping.seg", metas[0].SegmentPath);
Assert.Equal(0, metas[0].Offset);
Assert.Equal(metas[0].Length, metas[1].Offset); // 同一个段文件里依次追加
Assert.Equal(a.Bytes.ToArray(), await raw.ReadAsync(metas[0], default));
@@ -72,7 +72,7 @@ public sealed class StorageTests : IDisposable
var raw = new SqliteRawStore(_db);
var evil = new NodeId("../../../outside");
Assert.True(await raw.AppendAsync(Chunk(evil, "../etc", 1, "2026-01-01"), default));
Assert.True(await raw.AppendAsync(Chunk(s_agent, "udp_echo", 2, "2026-01-01", test: new TestId("..\\..\\x"), phase: 0), default));
Assert.True(await raw.AppendAsync(Chunk(s_agent, "quic_ping", 2, "2026-01-01", test: new TestId("..\\..\\x"), phase: 0), default));
foreach (string file in Directory.EnumerateFiles(_dir, "*.seg", SearchOption.AllDirectories))
{
@@ -80,7 +80,7 @@ public sealed class StorageTests : IDisposable
}
Assert.False(File.Exists(Path.Combine(_dir, "outside")));
await Assert.ThrowsAsync<ArgumentException>(async () => await raw.AppendAsync(Chunk(s_agent, "udp_echo", 3, "../2026"), default));
await Assert.ThrowsAsync<ArgumentException>(async () => await raw.AppendAsync(Chunk(s_agent, "quic_ping", 3, "../2026"), default));
}
[Fact]
@@ -90,19 +90,19 @@ public sealed class StorageTests : IDisposable
var testId = new TestId("tst_one");
for (int i = 0; i < 6; i++)
{
await raw.AppendAsync(Chunk(s_agent, "udp_echo", i * 10_000L, "2026-01-01"), default);
await raw.AppendAsync(Chunk(s_agent, "quic_ping", i * 10_000L, "2026-01-01"), default);
}
await raw.AppendAsync(Chunk(s_agent, "tcp_echo", 5_000, "2026-01-01"), default);
await raw.AppendAsync(Chunk(new NodeId("agt_other"), "udp_echo", 5_000, "2026-01-01"), default);
await raw.AppendAsync(Chunk(s_agent, "udp_train", 0, "2026-01-01", test: testId, phase: 1), default);
await raw.AppendAsync(Chunk(s_agent, "tcp_ping", 5_000, "2026-01-01"), default);
await raw.AppendAsync(Chunk(new NodeId("agt_other"), "quic_ping", 5_000, "2026-01-01"), default);
await raw.AppendAsync(Chunk(s_agent, "quic_stream", 0, "2026-01-01", test: testId, phase: 1), default);
Assert.Equal(8, (await raw.FindAsync(new RawQuery(s_agent), default)).Count);
Assert.Equal(6, (await raw.FindAsync(new RawQuery(s_agent, Kind: "udp_echo"), default)).Count);
Assert.Equal(6, (await raw.FindAsync(new RawQuery(s_agent, Kind: "quic_ping"), default)).Count);
Assert.Single(await raw.FindAsync(new RawQuery(TestId: testId), default));
Assert.Equal(3, (await raw.FindAsync(new RawQuery(s_agent, Kind: "udp_echo", FromMs: 10_000, ToMs: 40_000), default)).Count);
Assert.Equal(2, (await raw.FindAsync(new RawQuery(s_agent, Kind: "udp_echo", Limit: 2), default)).Count);
Assert.Equal(4, (await raw.FindAsync(new RawQuery(s_agent, Kind: "udp_echo", AfterStartTs: 20_000), default)).Count);
Assert.Equal(3, (await raw.FindAsync(new RawQuery(s_agent, Kind: "quic_ping", FromMs: 10_000, ToMs: 40_000), default)).Count);
Assert.Equal(2, (await raw.FindAsync(new RawQuery(s_agent, Kind: "quic_ping", Limit: 2), default)).Count);
Assert.Equal(4, (await raw.FindAsync(new RawQuery(s_agent, Kind: "quic_ping", AfterStartTs: 20_000), default)).Count);
IReadOnlyList<(NodeId AgentId, string Series)> series = await raw.ListSeriesAsync(0, 100_000, default);
Assert.Equal(2, series.Count); // 测试的块不算持续序列
@@ -112,9 +112,9 @@ public sealed class StorageTests : IDisposable
public async Task Raw_RecoverTruncatesAnUnindexedTail_AndLaterAppendsStayConsistent()
{
var raw = new SqliteRawStore(_db);
RawChunkAppend first = Chunk(s_agent, "udp_echo", 1, "2026-01-01");
RawChunkAppend first = Chunk(s_agent, "quic_ping", 1, "2026-01-01");
await raw.AppendAsync(first, default);
string segment = Directory.EnumerateFiles(_dir, "udp_echo.seg", SearchOption.AllDirectories).Single();
string segment = Directory.EnumerateFiles(_dir, "quic_ping.seg", SearchOption.AllDirectories).Single();
long indexedEnd = new FileInfo(segment).Length;
// 追加之后、写索引之前崩溃:段文件尾部多了没有索引记录的字节
@@ -124,7 +124,7 @@ public sealed class StorageTests : IDisposable
await raw.RecoverAsync(default);
Assert.Equal(indexedEnd, new FileInfo(segment).Length);
RawChunkAppend second = Chunk(s_agent, "udp_echo", 2, "2026-01-01");
RawChunkAppend second = Chunk(s_agent, "quic_ping", 2, "2026-01-01");
await raw.AppendAsync(second, default);
IReadOnlyList<RawChunkMeta> metas = await raw.FindAsync(new RawQuery(s_agent), default);
Assert.Equal(first.Bytes.ToArray(), await raw.ReadAsync(metas[0], default));
@@ -137,10 +137,10 @@ public sealed class StorageTests : IDisposable
{
var raw = new SqliteRawStore(_db);
var testId = new TestId("tst_old");
await raw.AppendAsync(Chunk(s_agent, "udp_echo", 1, "2026-01-01"), default);
await raw.AppendAsync(Chunk(s_agent, "udp_echo", 2, "2026-03-01"), default);
await raw.AppendAsync(Chunk(s_agent, "udp_train", 3, "2026-01-01", test: testId, phase: 0), default);
await raw.AppendAsync(Chunk(s_agent, "udp_train", 4, "2026-02-01", test: new TestId("tst_mid"), phase: 0), default);
await raw.AppendAsync(Chunk(s_agent, "quic_ping", 1, "2026-01-01"), default);
await raw.AppendAsync(Chunk(s_agent, "quic_ping", 2, "2026-03-01"), default);
await raw.AppendAsync(Chunk(s_agent, "quic_stream", 3, "2026-01-01", test: testId, phase: 0), default);
await raw.AppendAsync(Chunk(s_agent, "quic_stream", 4, "2026-02-01", test: new TestId("tst_mid"), phase: 0), default);
// 持续任务保留到 2026-02-01 之后,测试保留到 2026-01-15 之后
int removed = await raw.DeleteExpiredAsync(new DateOnly(2026, 2, 1), new DateOnly(2026, 1, 15), default);
@@ -365,6 +365,44 @@ public sealed class StorageTests : IDisposable
Assert.Equal(200, (await settings.GetAllAsync(default)).Count);
}
[Fact]
public void Schema_UpgradesVersion1_ByRenamingTheKinds()
{
string dir = Path.Combine(Path.GetTempPath(), "moshiro-schema-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(dir);
string path = Path.Combine(dir, "moshiro.db");
try
{
using (var db = new SqliteDatabase(dir))
{
}
SqliteConnection.ClearAllPools();
using (var connection = new SqliteConnection($"Data Source={path}"))
{
connection.Open();
connection.Exec("INSERT INTO tasks (id, scope, kind, schedule, updated_at) VALUES ('tsk_a', 'line', 'udp_echo', 'continuous', 1), ('tsk_b', 'line', 'connect', 'continuous', 1)");
connection.Exec("PRAGMA user_version = 1");
}
SqliteConnection.ClearAllPools();
using (var db = new SqliteDatabase(dir))
{
}
SqliteConnection.ClearAllPools();
using var check = new SqliteConnection($"Data Source={path}");
check.Open();
Assert.Equal(2, check.Scalar<int>("PRAGMA user_version"));
Assert.Equal("quic_ping,tcp_connect", string.Join(',', new[] { "tsk_a", "tsk_b" }.Select(id => check.Scalar<string>($"SELECT kind FROM tasks WHERE id = '{id}'"))));
}
finally
{
SqliteConnection.ClearAllPools();
Directory.Delete(dir, recursive: true);
}
}
[Fact]
public void Schema_RefusesADatabaseFromANewerBuild()
{