mirror of
https://github.com/googleapis/genai-toolbox.git
synced 2026-02-05 12:45:11 -05:00
This change introduces robust validation for Firestore document and collection paths across various Firestore tools. Key changes include: * **Path Validation:** Ensures that all Firestore paths used in tools are relative and adhere to correct formatting, preventing issues with absolute paths or malformed segments. * **Improved Parameter Descriptions:** Updates the descriptions for Firestore tool parameters to clearly specify the expectation of relative paths (e.g., `users/userId` or `users/userId/posts/postId`) instead of absolute paths. * **New Utility:** Adds `internal/tools/firestore/util/validator.go` and its corresponding test file for path validation logic. --------- Co-authored-by: prernakakkar-google <158031829+prernakakkar-google@users.noreply.github.com>
219 lines
5.1 KiB
Go
219 lines
5.1 KiB
Go
// Copyright 2025 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package util
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateCollectionPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
// Valid cases
|
|
{
|
|
name: "valid root collection",
|
|
path: "users",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid subcollection",
|
|
path: "users/user123/posts",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid deeply nested",
|
|
path: "users/user123/posts/post456/comments",
|
|
wantErr: false,
|
|
},
|
|
|
|
// Invalid cases
|
|
{
|
|
name: "empty path",
|
|
path: "",
|
|
wantErr: true,
|
|
errMsg: "collection path cannot be empty",
|
|
},
|
|
{
|
|
name: "even segments (document path)",
|
|
path: "users/user123",
|
|
wantErr: true,
|
|
errMsg: "must have an odd number of segments",
|
|
},
|
|
{
|
|
name: "absolute path",
|
|
path: "projects/my-project/databases/(default)/documents/users",
|
|
wantErr: true,
|
|
errMsg: "path must be relative",
|
|
},
|
|
{
|
|
name: "reserved prefix __",
|
|
path: "__users",
|
|
wantErr: true,
|
|
errMsg: "collection ID cannot start with '__'",
|
|
},
|
|
{
|
|
name: "dot segment",
|
|
path: "users/./posts",
|
|
wantErr: true,
|
|
errMsg: "segment cannot be '.'",
|
|
},
|
|
{
|
|
name: "double slashes",
|
|
path: "users//posts",
|
|
wantErr: true,
|
|
errMsg: "segment cannot be empty",
|
|
},
|
|
{
|
|
name: "trailing slash",
|
|
path: "users/",
|
|
wantErr: true,
|
|
errMsg: "must have an odd number of segments",
|
|
},
|
|
{
|
|
name: "whitespace only segment",
|
|
path: "users/ /posts",
|
|
wantErr: true,
|
|
errMsg: "segment cannot be only whitespace",
|
|
},
|
|
{
|
|
name: "tab whitespace segment",
|
|
path: "users/\t/posts",
|
|
wantErr: true,
|
|
errMsg: "segment cannot be only whitespace",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := ValidateCollectionPath(tt.path)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("ValidateCollectionPath(%q) expected error but got none", tt.path)
|
|
} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
|
|
t.Errorf("ValidateCollectionPath(%q) error = %v, want error containing %q", tt.path, err, tt.errMsg)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("ValidateCollectionPath(%q) unexpected error: %v", tt.path, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateDocumentPath(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
wantErr bool
|
|
errMsg string
|
|
}{
|
|
// Valid cases
|
|
{
|
|
name: "valid root document",
|
|
path: "users/user123",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid nested document",
|
|
path: "users/user123/posts/post456",
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "valid deeply nested",
|
|
path: "users/user123/posts/post456/comments/comment789",
|
|
wantErr: false,
|
|
},
|
|
|
|
// Invalid cases
|
|
{
|
|
name: "empty path",
|
|
path: "",
|
|
wantErr: true,
|
|
errMsg: "document path cannot be empty",
|
|
},
|
|
{
|
|
name: "odd segments (collection path)",
|
|
path: "users",
|
|
wantErr: true,
|
|
errMsg: "must have an even number of segments",
|
|
},
|
|
{
|
|
name: "absolute path",
|
|
path: "projects/my-project/databases/(default)/documents/users/user123",
|
|
wantErr: true,
|
|
errMsg: "path must be relative",
|
|
},
|
|
{
|
|
name: "reserved prefix __",
|
|
path: "users/__user123",
|
|
wantErr: true,
|
|
errMsg: "document ID cannot start with '__'",
|
|
},
|
|
{
|
|
name: "double dot segment",
|
|
path: "users/..",
|
|
wantErr: true,
|
|
errMsg: "segment cannot be '.'",
|
|
},
|
|
{
|
|
name: "double slashes in document path",
|
|
path: "users//user123",
|
|
wantErr: true,
|
|
errMsg: "must have an even number of segments",
|
|
},
|
|
{
|
|
name: "trailing slash document",
|
|
path: "users/user123/",
|
|
wantErr: true,
|
|
errMsg: "must have an even number of segments",
|
|
},
|
|
{
|
|
name: "whitespace only document ID",
|
|
path: "users/ ",
|
|
wantErr: true,
|
|
errMsg: "segment cannot be only whitespace",
|
|
},
|
|
{
|
|
name: "whitespace in middle segment",
|
|
path: "users/user123/posts/ \t ",
|
|
wantErr: true,
|
|
errMsg: "segment cannot be only whitespace",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := ValidateDocumentPath(tt.path)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("ValidateDocumentPath(%q) expected error but got none", tt.path)
|
|
} else if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
|
|
t.Errorf("ValidateDocumentPath(%q) error = %v, want error containing %q", tt.path, err, tt.errMsg)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("ValidateDocumentPath(%q) unexpected error: %v", tt.path, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|