Fix SCUM file list capability fallback
This commit is contained in:
@@ -259,14 +259,14 @@ func TestCoreAPIServerFileWorkspaceRoutesAreScoped(t *testing.T) {
|
|||||||
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, adminSession)
|
putJSONWithAuth[dto.RuntimeBindingResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/runtime-binding", dto.RuntimeBindingUpdateRequest{ProfileKey: "local", Bindings: map[string]string{}}, adminSession)
|
||||||
|
|
||||||
workspace := getJSONWithAuth[dto.ServerFileWorkspaceResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/files/workspace", adminSession)
|
workspace := getJSONWithAuth[dto.ServerFileWorkspaceResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/files/workspace", adminSession)
|
||||||
if workspace.DefaultDirectoryKey != "server-root" || workspace.Transfer.Channel != "run-file-transfer" || workspace.DeclaredOnly || len(workspace.Directories) != 1 || workspace.Directories[0].Label != "服务器根目录" || len(workspace.Files) != 0 {
|
if workspace.DefaultDirectoryKey != "scum-config" || workspace.Transfer.Channel != "run-file-transfer" || workspace.DeclaredOnly || len(workspace.Directories) != 2 || workspace.Directories[0].Label != "服务器配置" || len(workspace.Files) != 2 {
|
||||||
t.Fatalf("unexpected workspace: %+v", workspace)
|
t.Fatalf("unexpected workspace: %+v", workspace)
|
||||||
}
|
}
|
||||||
list := getJSONWithAuth[dto.ServerFileListResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/files/list?directoryKey=server-root", adminSession)
|
list := getJSONWithAuth[dto.ServerFileListResponse](t, router, "/api/v1/server-instances/"+instance.ID+"/files/list?directoryKey=scum-config", adminSession)
|
||||||
if list.State != "declared" || list.DirectoryKey != "server-root" || list.Entries == nil || !strings.Contains(list.Reason, "服务器文件缓存") {
|
if list.State != "declared" || list.DirectoryKey != "scum-config" || len(list.Entries) != 2 || list.Entries[1].Name != "ServerSettings.ini" || !strings.Contains(list.Reason, "服务器文件缓存") {
|
||||||
t.Fatalf("expected generic root file list, got %+v", list)
|
t.Fatalf("expected declared SCUM file list, got %+v", list)
|
||||||
}
|
}
|
||||||
refreshRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+instance.ID+"/files/refresh", dto.ServerFileListRequest{DirectoryKey: "server-root", IdempotencyKey: "api-file-list-refresh"}, adminSession)
|
refreshRecorder := requestJSONWithAuth(t, router, http.MethodPost, "/api/v1/server-instances/"+instance.ID+"/files/refresh", dto.ServerFileListRequest{DirectoryKey: "scum-config", IdempotencyKey: "api-file-list-refresh"}, adminSession)
|
||||||
assertStatus(t, refreshRecorder, http.StatusAccepted)
|
assertStatus(t, refreshRecorder, http.StatusAccepted)
|
||||||
refresh := decodeBody[dto.ServerFileListResponse](t, refreshRecorder)
|
refresh := decodeBody[dto.ServerFileListResponse](t, refreshRecorder)
|
||||||
if refresh.State != "pending" || refresh.Job == nil || refresh.Job.Capability != domain.JobCapabilityFilesList {
|
if refresh.State != "pending" || refresh.Job == nil || refresh.Job.Capability != domain.JobCapabilityFilesList {
|
||||||
|
|||||||
@@ -973,6 +973,10 @@ func TestPluginFileWorkspaceDoesNotConstrainServerFileDispatch(t *testing.T) {
|
|||||||
func TestServerFileListReportsFailedRuntimeRefresh(t *testing.T) {
|
func TestServerFileListReportsFailedRuntimeRefresh(t *testing.T) {
|
||||||
svc := newTestCoreService()
|
svc := newTestCoreService()
|
||||||
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
||||||
|
endpoint.Capabilities = append(endpoint.Capabilities, domain.JobCapabilityFilesList)
|
||||||
|
if err := svc.store.RunEndpoints().Update(endpoint); err != nil {
|
||||||
|
t.Fatalf("update file list capability: %v", err)
|
||||||
|
}
|
||||||
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "user-file-list-failure", DisplayName: "File List Failure", Email: "file-list-failure@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
|
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "user-file-list-failure", DisplayName: "File List Failure", Email: "file-list-failure@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
|
||||||
instance, err := svc.CreateServerInstanceForSession(ownerSession, domain.ServerInstance{ID: "server-file-list-failure", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "File List Failure Server", State: domain.ServerInstanceStateRunning})
|
instance, err := svc.CreateServerInstanceForSession(ownerSession, domain.ServerInstance{ID: "server-file-list-failure", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "File List Failure Server", State: domain.ServerInstanceStateRunning})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -999,6 +1003,36 @@ func TestServerFileListReportsFailedRuntimeRefresh(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServerFileListFallsBackToPluginWorkspaceWithoutRunListCapability(t *testing.T) {
|
||||||
|
svc := newTestCoreService()
|
||||||
|
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
||||||
|
plugin.FileWorkspace = scumTestFileWorkspace()
|
||||||
|
if err := svc.store.GamePlugins().Update(plugin); err != nil {
|
||||||
|
t.Fatalf("update plugin workspace: %v", err)
|
||||||
|
}
|
||||||
|
ownerSession := createServiceUserAndLogin(t, svc, domain.User{ID: "user-file-list-fallback", DisplayName: "File List Fallback", Email: "file-list-fallback@example.test", Roles: []string{"server-owner"}, PasswordHash: "secret-password"})
|
||||||
|
instance, err := svc.CreateServerInstanceForSession(ownerSession, domain.ServerInstance{ID: "server-file-list-fallback", PluginID: plugin.ID, RunEndpointID: endpoint.ID, Name: "File List Fallback Server", State: domain.ServerInstanceStateRunning})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("create server: %v", err)
|
||||||
|
}
|
||||||
|
createCompleteRuntimeBinding(t, svc, instance, "local")
|
||||||
|
|
||||||
|
refresh, err := svc.RefreshServerFileListForSession(ownerSession, domain.ServerFileListRequest{ServerInstanceID: instance.ID, DirectoryKey: "scum-config", IdempotencyKey: "idem-file-list-fallback"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("refresh file list fallback: %v", err)
|
||||||
|
}
|
||||||
|
if refresh.State != "ready" || refresh.Job.ID != "" || len(refresh.Entries) != 2 || refresh.Entries[1].LogicalKey != "scum-server-settings" || !strings.Contains(refresh.Reason, "未声明 files.list") {
|
||||||
|
t.Fatalf("expected plugin workspace fallback, got %+v", refresh)
|
||||||
|
}
|
||||||
|
jobs, err := svc.store.Jobs().List(domain.JobFilter{ServerInstanceID: instance.ID})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("list fallback jobs: %v", err)
|
||||||
|
}
|
||||||
|
if len(jobs) != 0 {
|
||||||
|
t.Fatalf("unsupported Run capability must not create a failed refresh job, got %+v", jobs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeclaredFileReadSnapshotProjectionStatesAndRedaction(t *testing.T) {
|
func TestDeclaredFileReadSnapshotProjectionStatesAndRedaction(t *testing.T) {
|
||||||
svc := newTestCoreService()
|
svc := newTestCoreService()
|
||||||
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
plugin, endpoint := createPluginAndRunEndpoint(t, svc)
|
||||||
|
|||||||
@@ -150,6 +150,22 @@ func (svc *CoreService) RefreshServerFileListForSession(sessionID string, reques
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ServerFileListResult{}, err
|
return domain.ServerFileListResult{}, err
|
||||||
}
|
}
|
||||||
|
endpoint, err := svc.GetRunEndpoint(ctx.Instance.RunEndpointID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.ServerFileListResult{}, err
|
||||||
|
}
|
||||||
|
entries := filterServerFileEntries(serverFileEntriesFromDeclaredWorkspace(ctx.Workspace, request.DirectoryKey), request.Query)
|
||||||
|
if !containsString(endpoint.Capabilities, domain.JobCapabilityFilesList) {
|
||||||
|
return domain.CopyServerFileListResult(domain.ServerFileListResult{
|
||||||
|
ServerInstanceID: ctx.Instance.ID,
|
||||||
|
PluginID: ctx.Plugin.ID,
|
||||||
|
DirectoryKey: request.DirectoryKey,
|
||||||
|
Path: request.Path,
|
||||||
|
State: "ready",
|
||||||
|
Entries: entries,
|
||||||
|
Reason: "当前 Run 未声明 files.list,已展示插件声明的逻辑文件。",
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
job, err := svc.CreateJob(domain.Job{
|
job, err := svc.CreateJob(domain.Job{
|
||||||
ID: jobIDFromParts("job-file-list", request.ServerInstanceID, request.IdempotencyKey),
|
ID: jobIDFromParts("job-file-list", request.ServerInstanceID, request.IdempotencyKey),
|
||||||
ServerInstanceID: ctx.Instance.ID,
|
ServerInstanceID: ctx.Instance.ID,
|
||||||
@@ -169,7 +185,6 @@ func (svc *CoreService) RefreshServerFileListForSession(sessionID string, reques
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.ServerFileListResult{}, err
|
return domain.ServerFileListResult{}, err
|
||||||
}
|
}
|
||||||
entries := filterServerFileEntries(serverFileEntriesFromDeclaredWorkspace(ctx.Workspace, request.DirectoryKey), request.Query)
|
|
||||||
return domain.CopyServerFileListResult(domain.ServerFileListResult{ServerInstanceID: ctx.Instance.ID, PluginID: ctx.Plugin.ID, DirectoryKey: request.DirectoryKey, Path: request.Path, State: "pending", Entries: entries, Job: job, Reason: "目录刷新任务已派发到 Run。"}), nil
|
return domain.CopyServerFileListResult(domain.ServerFileListResult{ServerInstanceID: ctx.Instance.ID, PluginID: ctx.Plugin.ID, DirectoryKey: request.DirectoryKey, Path: request.Path, State: "pending", Entries: entries, Job: job, Reason: "目录刷新任务已派发到 Run。"}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,7 +380,14 @@ func (svc *CoreService) serverFileContextForSession(sessionID string, serverInst
|
|||||||
return serverFileContext{User: user, Instance: instance, Plugin: plugin, Directory: directory, Workspace: workspace, Scope: svc.runtimeProfileScope(instance.ID)}, nil
|
return serverFileContext{User: user, Instance: instance, Plugin: plugin, Directory: directory, Workspace: workspace, Scope: svc.runtimeProfileScope(instance.ID)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func effectiveServerFileWorkspace(_ domain.GamePlugin) domain.PluginFileWorkspace {
|
func effectiveServerFileWorkspace(plugin domain.GamePlugin) domain.PluginFileWorkspace {
|
||||||
|
workspace := domain.CopyPluginFileWorkspace(plugin.FileWorkspace)
|
||||||
|
if serverFileWorkspaceIsPluginDeclared(workspace) {
|
||||||
|
if workspace.DefaultDirectoryKey == "" && len(workspace.Directories) > 0 {
|
||||||
|
workspace.DefaultDirectoryKey = workspace.Directories[0].Key
|
||||||
|
}
|
||||||
|
return workspace
|
||||||
|
}
|
||||||
return domain.PluginFileWorkspace{DefaultDirectoryKey: serverFileDefaultDirectoryKey, Directories: []domain.PluginLogicalDirectory{{Key: serverFileDefaultDirectoryKey, Label: "服务器根目录", Scope: "config"}}, Files: []domain.PluginLogicalFile{}, ConfigFields: []domain.PluginConfigField{}}
|
return domain.PluginFileWorkspace{DefaultDirectoryKey: serverFileDefaultDirectoryKey, Directories: []domain.PluginLogicalDirectory{{Key: serverFileDefaultDirectoryKey, Label: "服务器根目录", Scope: "config"}}, Files: []domain.PluginLogicalFile{}, ConfigFields: []domain.PluginConfigField{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user