mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-10-30 08:03:00 +08:00
All public APIs now live in one tree, with a version in every package, so a future version can sit next to v1. * Proto packages are `substrate.<api-name>.v1`, so gRPC method names change, for example to `/substrate.ateapi.v1.Control/GetActor`. * Go packages for the protos are named after the API, for example `package ateapi`. * CRDs move from `ate.dev/v1alpha1` to `ate.dev/v1`. They live in `api/infra/k8s/ate/v1`. AI use: AI-assisted, I reviewed and tested it (test automation). Fixes #2753
165 lines
6.9 KiB
Go
165 lines
6.9 KiB
Go
// Copyright 2026 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 functionaltest
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"go.ate.dev/substrate/api/ateapi/v1"
|
|
"go.ate.dev/substrate/internal/resources"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
)
|
|
|
|
func wantCode(t *testing.T, op string, err error, want codes.Code) {
|
|
t.Helper()
|
|
if got := status.Code(err); got != want {
|
|
t.Fatalf("%s: code = %v, want %v (err: %v)", op, got, want, err)
|
|
}
|
|
}
|
|
|
|
func TestGlobalAccessPolicy_CRUD(t *testing.T) {
|
|
tc := setupTest(t, namespaceForTest("ns-global-access-policy"))
|
|
defer tc.cleanup()
|
|
ctx := context.Background()
|
|
|
|
_, err := tc.client.GetGlobalAccessPolicy(ctx, &ateapi.GetGlobalAccessPolicyRequest{Name: resources.SingletonName})
|
|
wantCode(t, "Get before create", err, codes.NotFound)
|
|
|
|
created, err := tc.client.CreateGlobalAccessPolicy(ctx, &ateapi.CreateGlobalAccessPolicyRequest{
|
|
AccessPolicy: &ateapi.AccessPolicy{
|
|
Metadata: &ateapi.ResourceMetadata{Name: "default"},
|
|
Bindings: []*ateapi.Binding{{Role: "owner", Members: []string{"user:alice"}}},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create failed: %v", err)
|
|
}
|
|
if md := created.GetMetadata(); md.GetName() != "default" || md.GetUid() == "" || md.GetVersion() != 1 {
|
|
t.Errorf("created metadata = %v, want name default, a uid and version 1", md)
|
|
}
|
|
|
|
_, err = tc.client.CreateGlobalAccessPolicy(ctx, &ateapi.CreateGlobalAccessPolicyRequest{
|
|
AccessPolicy: &ateapi.AccessPolicy{
|
|
Metadata: &ateapi.ResourceMetadata{Name: "default"},
|
|
Bindings: []*ateapi.Binding{{Role: "owner", Members: []string{"user:alice"}}},
|
|
},
|
|
})
|
|
wantCode(t, "second Create", err, codes.AlreadyExists)
|
|
|
|
got, err := tc.client.GetGlobalAccessPolicy(ctx, &ateapi.GetGlobalAccessPolicyRequest{Name: resources.SingletonName})
|
|
if err != nil {
|
|
t.Fatalf("Get failed: %v", err)
|
|
}
|
|
if diff := cmp.Diff(created, got, protocmp.Transform()); diff != "" {
|
|
t.Errorf("Get mismatch (-created +got):\n%s", diff)
|
|
}
|
|
|
|
update := proto.Clone(created).(*ateapi.AccessPolicy)
|
|
update.Bindings = []*ateapi.Binding{
|
|
{Role: "owner", Members: []string{"user:alice"}},
|
|
{Role: "viewer", Members: []string{"user:bob"}},
|
|
}
|
|
updated, err := tc.client.UpdateGlobalAccessPolicy(ctx, &ateapi.UpdateGlobalAccessPolicyRequest{AccessPolicy: update})
|
|
if err != nil {
|
|
t.Fatalf("Update failed: %v", err)
|
|
}
|
|
if updated.GetMetadata().GetVersion() != created.GetMetadata().GetVersion()+1 {
|
|
t.Errorf("updated version = %d, want %d", updated.GetMetadata().GetVersion(), created.GetMetadata().GetVersion()+1)
|
|
}
|
|
if diff := cmp.Diff(update.GetBindings(), updated.GetBindings(), protocmp.Transform()); diff != "" {
|
|
t.Errorf("updated bindings mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
// The update above consumed created's version.
|
|
_, err = tc.client.UpdateGlobalAccessPolicy(ctx, &ateapi.UpdateGlobalAccessPolicyRequest{AccessPolicy: update})
|
|
wantCode(t, "stale Update", err, codes.Aborted)
|
|
}
|
|
|
|
func TestAtespaceAccessPolicy_CRUD(t *testing.T) {
|
|
tc := setupTest(t, namespaceForTest("ns-atespace-access-policy"))
|
|
defer tc.cleanup()
|
|
ctx := context.Background()
|
|
createAtespace(t, tc, "team-a")
|
|
ref := &ateapi.ObjectRef{Name: "team-a"}
|
|
policy := &ateapi.AccessPolicy{
|
|
Metadata: &ateapi.ResourceMetadata{Name: "default"},
|
|
Bindings: []*ateapi.Binding{{Role: "editor", Members: []string{"user:alice"}}},
|
|
}
|
|
|
|
_, err := tc.client.CreateAtespaceAccessPolicy(ctx, &ateapi.CreateAtespaceAccessPolicyRequest{
|
|
Atespace: &ateapi.ObjectRef{Name: "missing"},
|
|
AccessPolicy: policy,
|
|
})
|
|
wantCode(t, "Create on missing atespace", err, codes.FailedPrecondition)
|
|
|
|
_, err = tc.client.GetAtespaceAccessPolicy(ctx, &ateapi.GetAtespaceAccessPolicyRequest{Atespace: ref, Name: resources.SingletonName})
|
|
wantCode(t, "Get before create", err, codes.NotFound)
|
|
|
|
created, err := tc.client.CreateAtespaceAccessPolicy(ctx, &ateapi.CreateAtespaceAccessPolicyRequest{Atespace: ref, AccessPolicy: policy})
|
|
if err != nil {
|
|
t.Fatalf("Create failed: %v", err)
|
|
}
|
|
_, err = tc.client.CreateAtespaceAccessPolicy(ctx, &ateapi.CreateAtespaceAccessPolicyRequest{Atespace: ref, AccessPolicy: policy})
|
|
wantCode(t, "second Create", err, codes.AlreadyExists)
|
|
|
|
got, err := tc.client.GetAtespaceAccessPolicy(ctx, &ateapi.GetAtespaceAccessPolicyRequest{Atespace: ref, Name: resources.SingletonName})
|
|
if err != nil {
|
|
t.Fatalf("Get failed: %v", err)
|
|
}
|
|
if diff := cmp.Diff(created, got, protocmp.Transform()); diff != "" {
|
|
t.Errorf("Get mismatch (-created +got):\n%s", diff)
|
|
}
|
|
|
|
update := proto.Clone(created).(*ateapi.AccessPolicy)
|
|
update.Bindings = []*ateapi.Binding{{Role: "viewer", Members: []string{"user:bob"}}}
|
|
updated, err := tc.client.UpdateAtespaceAccessPolicy(ctx, &ateapi.UpdateAtespaceAccessPolicyRequest{Atespace: ref, AccessPolicy: update})
|
|
if err != nil {
|
|
t.Fatalf("Update failed: %v", err)
|
|
}
|
|
if diff := cmp.Diff(update.GetBindings(), updated.GetBindings(), protocmp.Transform()); diff != "" {
|
|
t.Errorf("updated bindings mismatch (-want +got):\n%s", diff)
|
|
}
|
|
|
|
_, err = tc.client.DeleteAtespaceAccessPolicy(ctx, &ateapi.DeleteAtespaceAccessPolicyRequest{
|
|
Atespace: ref,
|
|
Name: resources.SingletonName,
|
|
Options: &ateapi.DeleteOptions{Version: created.GetMetadata().GetVersion()},
|
|
})
|
|
wantCode(t, "Delete with stale version", err, codes.Aborted)
|
|
|
|
if _, err := tc.client.DeleteAtespaceAccessPolicy(ctx, &ateapi.DeleteAtespaceAccessPolicyRequest{Atespace: ref, Name: resources.SingletonName}); err != nil {
|
|
t.Fatalf("Delete failed: %v", err)
|
|
}
|
|
_, err = tc.client.GetAtespaceAccessPolicy(ctx, &ateapi.GetAtespaceAccessPolicyRequest{Atespace: ref, Name: resources.SingletonName})
|
|
wantCode(t, "Get after delete", err, codes.NotFound)
|
|
_, err = tc.client.DeleteAtespaceAccessPolicy(ctx, &ateapi.DeleteAtespaceAccessPolicyRequest{Atespace: ref, Name: resources.SingletonName})
|
|
wantCode(t, "second Delete", err, codes.NotFound)
|
|
|
|
// Deleting the atespace removes its policy with it.
|
|
if _, err := tc.client.CreateAtespaceAccessPolicy(ctx, &ateapi.CreateAtespaceAccessPolicyRequest{Atespace: ref, AccessPolicy: policy}); err != nil {
|
|
t.Fatalf("re-Create failed: %v", err)
|
|
}
|
|
if _, err := tc.client.DeleteAtespace(ctx, &ateapi.DeleteAtespaceRequest{Atespace: ref}); err != nil {
|
|
t.Fatalf("DeleteAtespace failed: %v", err)
|
|
}
|
|
_, err = tc.client.GetAtespaceAccessPolicy(ctx, &ateapi.GetAtespaceAccessPolicyRequest{Atespace: ref, Name: resources.SingletonName})
|
|
wantCode(t, "Get after DeleteAtespace", err, codes.NotFound)
|
|
}
|