Stream live server logs over SSE

This commit is contained in:
npc0-hue
2026-08-03 22:28:54 +08:00
parent 5d4fca14f9
commit 7eac1926dd
48 changed files with 1526 additions and 263 deletions
+46
View File
@@ -98,6 +98,52 @@ func TestRuntimeBindingValidationAndLifecycleGating(t *testing.T) {
if err != nil || result.Job.TargetKey != "actions/start.json" || result.Job.ExecutionInput.WorkspaceScope != "local" {
t.Fatalf("expected complete binding to permit start, result=%+v err=%v", result, err)
}
view, err = svc.UpdateServerRuntimeBindingForSession(ownerSession, instance.ID, domain.RuntimeBindingUpdate{ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root-updated"}})
if err != nil || view.Status != domain.RuntimeBindingStatusComplete {
t.Fatalf("expected active server runtime binding edits to remain available, view=%+v err=%v", view, err)
}
instance.State = domain.ServerInstanceStateDeleted
if err := svc.store.ServerInstances().Update(instance); err != nil {
t.Fatalf("mark deleted: %v", err)
}
if _, err := svc.UpdateServerRuntimeBindingForSession(ownerSession, instance.ID, domain.RuntimeBindingUpdate{ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root"}}); err == nil || !strings.Contains(err.Error(), "deleted") {
t.Fatalf("expected deleted server runtime binding edit rejection, got %v", err)
}
}
func TestRuntimeBindingLogSourcesAreConfigurableButNotRequired(t *testing.T) {
svc := newTestCoreService()
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
plugin.RuntimeProfiles = requiredRuntimeProfilesFixture()
plugin.RuntimeProfiles.LogSources = []domain.RuntimeLogSource{
{Key: "console-stdout", Kind: "process.stdout", TargetKey: "process/server", StreamKey: "console.stdout", CursorKind: "sequence", RetentionDays: 30},
{Key: "latest", Kind: "file.tail", TargetKey: "logs/latest", StreamKey: "latest-log", CursorKind: "offset", RetentionDays: 30},
}
if err := svc.store.GamePlugins().Update(plugin); err != nil {
t.Fatalf("update plugin profiles: %v", err)
}
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "runtime-logs-owner", DisplayName: "Runtime Logs Owner", Email: "runtime-logs-owner@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
instance, err := svc.CreateServerInstanceForSession(ownerSession, domain.ServerInstance{ID: "runtime-log-sources", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "Runtime Log Sources", State: domain.ServerInstanceStateRunning})
if err != nil {
t.Fatalf("create server: %v", err)
}
view, err := svc.UpdateServerRuntimeBindingForSession(ownerSession, instance.ID, domain.RuntimeBindingUpdate{ProfileKey: "local", Bindings: map[string]string{"server-root": "runtime.server-root", "rcon.password": "secret://runtime-log-sources/rcon"}})
if err != nil || view.Status != domain.RuntimeBindingStatusComplete || len(view.MissingKeys) != 0 {
t.Fatalf("log sources should not block runtime readiness: view=%+v err=%v", view, err)
}
seenLogs := map[string]domain.RuntimeBindingKeyView{}
for _, key := range view.Keys {
if strings.HasPrefix(key.Key, "logs/") || strings.HasPrefix(key.Key, "process/") {
seenLogs[key.Key] = key
}
}
for _, key := range []string{"logs/latest", "process/server"} {
item, ok := seenLogs[key]
if !ok || item.Required || item.Configured {
t.Fatalf("expected optional unconfigured log key %s, seen=%+v view=%+v", key, seenLogs, view)
}
}
}
func requiredRuntimeProfilesFixture() domain.GamePluginRuntimeProfiles {