From 8f8e4a881a2599f6474c46fa80f30d0d35d8aed9 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 1 Oct 2025 14:11:00 +0300 Subject: [PATCH] Dashboards: Avoid panic with invalid continue token (#111870) --- pkg/registry/apis/dashboard/legacy/token.go | 7 +- .../apis/dashboard/legacy/token_test.go | 118 ++++++++++++++++++ 2 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 pkg/registry/apis/dashboard/legacy/token_test.go diff --git a/pkg/registry/apis/dashboard/legacy/token.go b/pkg/registry/apis/dashboard/legacy/token.go index a3bc0e5fb90..a5de881550c 100644 --- a/pkg/registry/apis/dashboard/legacy/token.go +++ b/pkg/registry/apis/dashboard/legacy/token.go @@ -44,7 +44,9 @@ func readContinueToken(next string) (continueToken, error) { if sub[0] != "folder" { return token, fmt.Errorf("expected folder UID in third slug") } - token.folder = sub[1] + if len(sub) > 1 { + token.folder = sub[1] + } // // Check if the folder filter is the same from the previous query // if q.Requirements.Folder == nil { @@ -59,6 +61,5 @@ func readContinueToken(next string) (continueToken, error) { } func (r *continueToken) String() string { - return fmt.Sprintf("org:%d/start:%d/folder:%s", - r.orgId, r.id, r.folder) + return fmt.Sprintf("org:%d/start:%d/folder:%s", r.orgId, r.id, r.folder) } diff --git a/pkg/registry/apis/dashboard/legacy/token_test.go b/pkg/registry/apis/dashboard/legacy/token_test.go new file mode 100644 index 00000000000..e8bdf9d5402 --- /dev/null +++ b/pkg/registry/apis/dashboard/legacy/token_test.go @@ -0,0 +1,118 @@ +package legacy + +import ( + "testing" +) + +func TestReadContinueToken(t *testing.T) { + tests := []struct { + name string + input string + wantToken continueToken + wantErr bool + }{ + { + name: "empty token", + input: "", + wantToken: continueToken{ + orgId: 0, + id: 0, + folder: "", + }, + wantErr: false, + }, + { + name: "too few parts", + input: "org:1/start:2", + wantErr: true, + }, + { + name: "invalid org slug", + input: "foo:1/start:2/folder:abc", + wantErr: true, + }, + { + name: "invalid org id", + input: "org:abc/start:2/folder:abc", + wantErr: true, + }, + { + name: "invalid start slug", + input: "org:1/foo:2/folder:abc", + wantErr: true, + }, + { + name: "invalid start id", + input: "org:1/start:abc/folder:abc", + wantErr: true, + }, + { + name: "invalid folder slug", + input: "org:1/start:2/foo:abc", + wantErr: true, + }, + { + name: "valid token", + input: "org:42/start:100/folder:my-folder", + wantToken: continueToken{ + orgId: 42, + id: 100, + folder: "my-folder", + }, + wantErr: false, + }, + { + name: "valid token with empty folder", + input: "org:42/start:100/folder:", + wantToken: continueToken{ + orgId: 42, + id: 100, + folder: "", + }, + wantErr: false, + }, + { + name: "folder without value", + input: "org:42/start:100/folder", // missing trailing ":" + wantToken: continueToken{ + orgId: 42, + id: 100, + folder: "", + }, + wantErr: false, + }, + { + name: "missing folder", + input: "org:42/start:100", + wantToken: continueToken{ + orgId: 42, + id: 100, + folder: "", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + token, err := readContinueToken(tt.input) + if (err != nil) != tt.wantErr { + t.Errorf("readContinueToken() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr { + if token.orgId != tt.wantToken.orgId || token.id != tt.wantToken.id || token.folder != tt.wantToken.folder { + t.Errorf("readContinueToken() got = %+v, want %+v", token, tt.wantToken) + } + } + }) + } +} + +func TestContinueToken_String(t *testing.T) { + token := continueToken{orgId: 5, id: 10, folder: "abc"} + want := "org:5/start:10/folder:abc" + if got := token.String(); got != want { + t.Errorf("continueToken.String() = %q, want %q", got, want) + } +}