Files
larksuite-cli/internal/validate/resource_test.go
梁硕 83dfb068ad feat: open-source lark-cli — the official CLI for Lark/Feishu
Change-Id: I113d9cdb5403cec347efe4595415e34a18b7decf
2026-03-28 10:36:25 +08:00

109 lines
3.4 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package validate
import (
"strings"
"testing"
)
func TestResourceName_RejectsInjectionPatterns(t *testing.T) {
for _, tt := range []struct {
name string
input string
flag string
wantErr bool
}{
// ── GIVEN: normal API identifiers → THEN: allowed ──
{"normal id", "om_abc123", "--message", false},
{"file token", "boxcnXYZ789", "--file-token", false},
{"with slash", "files/abc", "--resource", false},
{"with underscore", "om_xxx_yyy", "--message", false},
{"with hyphen", "file-token-123", "--file-token", false},
{"single char", "a", "--id", false},
{"slash only", "/", "--id", false},
// ── GIVEN: path traversal attempts → THEN: rejected ──
{"dot-dot traversal", "../admin/secret", "--message", true},
{"mid path traversal", "files/../admin", "--message", true},
{"bare dot-dot", "..", "--message", true},
// ── GIVEN: URL special characters → THEN: rejected ──
{"question mark", "id?admin=true", "--id", true},
{"hash fragment", "id#section", "--id", true},
{"percent encoding", "id%2e%2e", "--id", true},
// ── GIVEN: control characters → THEN: rejected ──
{"null byte", "id\x00rest", "--id", true},
{"newline", "id\nrest", "--id", true},
{"tab", "id\trest", "--id", true},
{"escape char", "id\x1brest", "--id", true},
// ── GIVEN: dangerous Unicode → THEN: rejected ──
{"bidi RLO", "om_\u202Exxx", "--message", true},
{"zero width space", "om_\u200Bxxx", "--message", true},
{"BOM", "om_\uFEFFxxx", "--message", true},
// ── GIVEN: empty input → THEN: rejected ──
{"empty string", "", "--message", true},
} {
t.Run(tt.name, func(t *testing.T) {
// WHEN: ResourceName validates the identifier
err := ResourceName(tt.input, tt.flag)
// THEN: error matches expectation
if (err != nil) != tt.wantErr {
t.Errorf("ResourceName(%q, %q) error = %v, wantErr %v",
tt.input, tt.flag, err, tt.wantErr)
}
})
}
}
func TestResourceName_ErrorMessageContainsFlagName(t *testing.T) {
// GIVEN: an empty resource name with flag "--file-token"
// WHEN: ResourceName rejects it
err := ResourceName("", "--file-token")
// THEN: the error message contains the flag name for user-facing diagnostics
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "--file-token") {
t.Errorf("error should contain flag name, got: %s", err.Error())
}
}
func TestEncodePathSegment_EncodesSpecialCharacters(t *testing.T) {
for _, tt := range []struct {
name string
input string
want string
}{
// ── GIVEN: safe characters → THEN: unchanged ──
{"normal", "om_abc123", "om_abc123"},
{"empty", "", ""},
// ── GIVEN: URL-special characters → THEN: percent-encoded ──
{"slash", "a/b", "a%2Fb"},
{"space", "hello world", "hello%20world"},
{"question mark", "id?foo", "id%3Ffoo"},
{"hash", "id#bar", "id%23bar"},
{"dot-dot", "../admin", "..%2Fadmin"},
{"percent", "50%done", "50%25done"},
{"unicode", "报告", "%E6%8A%A5%E5%91%8A"},
} {
t.Run(tt.name, func(t *testing.T) {
// WHEN: EncodePathSegment encodes the input
got := EncodePathSegment(tt.input)
// THEN: output matches expected encoding
if got != tt.want {
t.Errorf("EncodePathSegment(%q) = %q, want %q", tt.input, got, tt.want)
}
})
}
}