mirror of
https://github.com/agent-substrate/substrate.git
synced 2026-10-02 03:24:42 +08:00
Unifies Actor, ActorTemplate and ActorTemplateVersion resource refs (#927)
Unifies Actor, ActorTemplate and ActorTemplateVersion resource refs
This commit is contained in:
@@ -185,7 +185,7 @@ func runCommand(ctx context.Context, atenetAddr string, actorRef resources.Actor
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Host = actorRef.DNSName()
|
||||
req.Host = resources.ActorDNSName(actorRef)
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
|
||||
@@ -93,6 +93,6 @@ func (c *RouterClient) request(ctx context.Context, method string, actorRef reso
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
// The router routes on the Host/:authority, not a header.
|
||||
req.Host = actorRef.DNSName()
|
||||
req.Host = resources.ActorDNSName(actorRef)
|
||||
return c.http.Do(req)
|
||||
}
|
||||
|
||||
@@ -1203,7 +1203,7 @@ func callActorPathOnce(t *testing.T, actorRef resources.ActorRef, method, path s
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
reqHttp.Host = actorRef.DNSName()
|
||||
reqHttp.Host = resources.ActorDNSName(actorRef)
|
||||
|
||||
httpClient := &http.Client{Timeout: 15 * time.Second}
|
||||
resp, err := httpClient.Do(reqHttp)
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
// 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 resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
|
||||
)
|
||||
|
||||
// ActorRef identifies an actor by the (atespace, name).
|
||||
//
|
||||
// ActorRef is the in-process form of the identity that ateapipb.ObjectRef
|
||||
// carries on the wire.
|
||||
type ActorRef struct {
|
||||
// Atespace is the isolation boundary the actor was created into. Required.
|
||||
Atespace string
|
||||
// Name is the actor's name, unique within Atespace. Required.
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r ActorRef) String() string {
|
||||
return r.Atespace + "/" + r.Name
|
||||
}
|
||||
|
||||
// LogValue implements slog.LogValuer so that slog.Any("actor", ref) records the
|
||||
// two components as a group ("actor.atespace", "actor.name") rather than
|
||||
// flattening them into one opaque string.
|
||||
func (r ActorRef) LogValue() slog.Value {
|
||||
return slog.GroupValue(
|
||||
slog.String("atespace", r.Atespace),
|
||||
slog.String("name", r.Name),
|
||||
)
|
||||
}
|
||||
|
||||
// DNSName returns the uniform DNS name the actor is reachable at.
|
||||
// This is: "<name>.<atespace>.actors.resources.substrate.ate.dev".
|
||||
func (r ActorRef) DNSName() string {
|
||||
return r.Name + "." + r.Atespace + "." + ActorDNSSuffix
|
||||
}
|
||||
|
||||
// ToObjectRef converts the reference to its wire form.
|
||||
func (r ActorRef) ToObjectRef() *ateapipb.ObjectRef {
|
||||
return &ateapipb.ObjectRef{Atespace: r.Atespace, Name: r.Name}
|
||||
}
|
||||
|
||||
// ActorRefFromObjectRef converts a wire reference to an ActorRef.
|
||||
func ActorRefFromObjectRef(ref *ateapipb.ObjectRef) ActorRef {
|
||||
return ActorRef{Atespace: ref.GetAtespace(), Name: ref.GetName()}
|
||||
}
|
||||
|
||||
// ActorRefFromActor returns the reference addressing the given actor.
|
||||
func ActorRefFromActor(a *ateapipb.Actor) ActorRef {
|
||||
return ActorRef{
|
||||
Atespace: a.GetMetadata().GetAtespace(),
|
||||
Name: a.GetMetadata().GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
// ParseActorDNSName parses a DNS name for a given actor.
|
||||
func ParseActorDNSName(name string) (ActorRef, error) {
|
||||
rest, found := strings.CutSuffix(strings.TrimSuffix(name, "."), "."+ActorDNSSuffix)
|
||||
if !found {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name: must end with %s, got %q", ActorDNSSuffix, name)
|
||||
}
|
||||
actorName, atespace, found := strings.Cut(rest, ".")
|
||||
if !found {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name: expected <actor_name>.<atespace>.%s, got %q", ActorDNSSuffix, name)
|
||||
}
|
||||
if !IsValidResourceName(actorName) {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name %q: %q is not a valid actor name", name, actorName)
|
||||
}
|
||||
if !IsValidResourceName(atespace) {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name %q: %q is not a valid atespace", name, atespace)
|
||||
}
|
||||
return ActorRef{Atespace: atespace, Name: actorName}, nil
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
// 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 resources
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
|
||||
)
|
||||
|
||||
func TestActorRefString(t *testing.T) {
|
||||
got := ActorRef{Atespace: "team-a", Name: "act-1"}.String()
|
||||
if want := "team-a/act-1"; got != want {
|
||||
t.Errorf("String() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorRefDNSName(t *testing.T) {
|
||||
actorRef := ActorRef{Atespace: "team-a", Name: "act-1"}
|
||||
|
||||
got := actorRef.DNSName()
|
||||
want := "act-1.team-a.actors.resources.substrate.ate.dev"
|
||||
if got != want {
|
||||
t.Errorf("DNSName() = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
parsed, err := ParseActorDNSName(got)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseActorDNSName(%q) error = %v", got, err)
|
||||
}
|
||||
if parsed != actorRef {
|
||||
t.Errorf("round-trip = %+v, want %+v", parsed, actorRef)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseActorDNSName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want ActorRef
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid", "act-1.team-a.actors.resources.substrate.ate.dev", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
|
||||
{"valid trailing dot", "act-1.team-a.actors.resources.substrate.ate.dev.", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
|
||||
{"wrong suffix", "act-1.team-a.example.com", ActorRef{}, true},
|
||||
{"missing atespace", "act-1.actors.resources.substrate.ate.dev", ActorRef{}, true},
|
||||
{"invalid actor name", "ACT-1.team-a.actors.resources.substrate.ate.dev", ActorRef{}, true},
|
||||
{"invalid atespace", "act-1.TEAM.actors.resources.substrate.ate.dev", ActorRef{}, true},
|
||||
{"host:port not accepted", "act-1.team-a.actors.resources.substrate.ate.dev:8080", ActorRef{}, true},
|
||||
{"empty", "", ActorRef{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseActorDNSName(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("ParseActorDNSName(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ParseActorDNSName(%q) = %+v, want %+v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorRefObjectRefRoundTrip(t *testing.T) {
|
||||
actorRef := ActorRef{Atespace: "team-a", Name: "act-1"}
|
||||
|
||||
obj := actorRef.ToObjectRef()
|
||||
if obj.GetAtespace() != "team-a" || obj.GetName() != "act-1" {
|
||||
t.Errorf("ToObjectRef() = (%q, %q), want (team-a, act-1)", obj.GetAtespace(), obj.GetName())
|
||||
}
|
||||
if got := ActorRefFromObjectRef(obj); got != actorRef {
|
||||
t.Errorf("round-trip = %+v, want %+v", got, actorRef)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorRefFromActor(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
actor *ateapipb.Actor
|
||||
want ActorRef
|
||||
}{
|
||||
{
|
||||
name: "populated",
|
||||
actor: &ateapipb.Actor{Metadata: &ateapipb.ResourceMetadata{
|
||||
Atespace: "team-a",
|
||||
Name: "act-1",
|
||||
}},
|
||||
want: ActorRef{Atespace: "team-a", Name: "act-1"},
|
||||
},
|
||||
{"nil actor", nil, ActorRef{}},
|
||||
{"nil metadata", &ateapipb.Actor{}, ActorRef{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ActorRefFromActor(tt.actor); got != tt.want {
|
||||
t.Errorf("ActorRefFromActor() = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
// 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 resources
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
|
||||
)
|
||||
|
||||
// ActorTemplateRef identifies an ActorTemplate by the (atespace, name).
|
||||
//
|
||||
// ActorTemplateRef is the in-process form of the identity that
|
||||
// ateapipb.ObjectRef carries on the wire.
|
||||
type ActorTemplateRef struct {
|
||||
// Atespace is the isolation boundary the template was created into. Required.
|
||||
Atespace string
|
||||
// Name is the template's name, unique within Atespace. Required.
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r ActorTemplateRef) String() string {
|
||||
return r.Atespace + "/" + r.Name
|
||||
}
|
||||
|
||||
// LogValue implements slog.LogValuer so that slog.Any("template", ref) records
|
||||
// the two components as a group ("template.atespace", "template.name") rather
|
||||
// than flattening them into one opaque string.
|
||||
func (r ActorTemplateRef) LogValue() slog.Value {
|
||||
return slog.GroupValue(
|
||||
slog.String("atespace", r.Atespace),
|
||||
slog.String("name", r.Name),
|
||||
)
|
||||
}
|
||||
|
||||
// ToObjectRef converts the reference to its wire form.
|
||||
func (r ActorTemplateRef) ToObjectRef() *ateapipb.ObjectRef {
|
||||
return &ateapipb.ObjectRef{Atespace: r.Atespace, Name: r.Name}
|
||||
}
|
||||
|
||||
// ActorTemplateRefFromObjectRef converts a wire reference to an ActorTemplateRef.
|
||||
func ActorTemplateRefFromObjectRef(ref *ateapipb.ObjectRef) ActorTemplateRef {
|
||||
return ActorTemplateRef{Atespace: ref.GetAtespace(), Name: ref.GetName()}
|
||||
}
|
||||
|
||||
// ActorTemplateRefFromActorTemplate returns the reference addressing the given
|
||||
// template.
|
||||
func ActorTemplateRefFromActorTemplate(t *ateapipb.ActorTemplate) ActorTemplateRef {
|
||||
return ActorTemplateRef{
|
||||
Atespace: t.GetMetadata().GetAtespace(),
|
||||
Name: t.GetMetadata().GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
// ActorTemplateVersionRef identifies an ActorTemplateVersion by the
|
||||
// (atespace, name).
|
||||
//
|
||||
// ActorTemplateVersionRef is the in-process form of the identity that
|
||||
// ateapipb.ObjectRef carries on the wire.
|
||||
type ActorTemplateVersionRef struct {
|
||||
// Atespace is the isolation boundary the version was created into. Required.
|
||||
Atespace string
|
||||
// Name is the version's name, unique within Atespace. Required.
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r ActorTemplateVersionRef) String() string {
|
||||
return r.Atespace + "/" + r.Name
|
||||
}
|
||||
|
||||
// LogValue implements slog.LogValuer so that slog.Any("version", ref) records
|
||||
// the two components as a group ("version.atespace", "version.name") rather
|
||||
// than flattening them into one opaque string.
|
||||
func (r ActorTemplateVersionRef) LogValue() slog.Value {
|
||||
return slog.GroupValue(
|
||||
slog.String("atespace", r.Atespace),
|
||||
slog.String("name", r.Name),
|
||||
)
|
||||
}
|
||||
|
||||
// ToObjectRef converts the reference to its wire form.
|
||||
func (r ActorTemplateVersionRef) ToObjectRef() *ateapipb.ObjectRef {
|
||||
return &ateapipb.ObjectRef{Atespace: r.Atespace, Name: r.Name}
|
||||
}
|
||||
|
||||
// ActorTemplateVersionRefFromObjectRef converts a wire reference to an
|
||||
// ActorTemplateVersionRef.
|
||||
func ActorTemplateVersionRefFromObjectRef(ref *ateapipb.ObjectRef) ActorTemplateVersionRef {
|
||||
return ActorTemplateVersionRef{Atespace: ref.GetAtespace(), Name: ref.GetName()}
|
||||
}
|
||||
|
||||
// ActorTemplateVersionRefFromActorTemplateVersion returns the reference
|
||||
// addressing the given version.
|
||||
func ActorTemplateVersionRefFromActorTemplateVersion(v *ateapipb.ActorTemplateVersion) ActorTemplateVersionRef {
|
||||
return ActorTemplateVersionRef{
|
||||
Atespace: v.GetMetadata().GetAtespace(),
|
||||
Name: v.GetMetadata().GetName(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// 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 resources
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
|
||||
)
|
||||
|
||||
// Resource is any Atespaced resource message carrying the common metadata.
|
||||
type Resource interface {
|
||||
GetMetadata() *ateapipb.ResourceMetadata
|
||||
}
|
||||
|
||||
// ResourceRef identifies an Atespaced resource by the (atespace, name).
|
||||
type ResourceRef[R Resource] struct {
|
||||
// Atespace is the isolation boundary the resource was created into. Required.
|
||||
Atespace string
|
||||
// Name is the resource's name, unique within Atespace. Required.
|
||||
Name string
|
||||
}
|
||||
|
||||
func (r ResourceRef[R]) String() string {
|
||||
return r.Atespace + "/" + r.Name
|
||||
}
|
||||
|
||||
// LogValue implements slog.LogValuer so that slog.Any("template", ref) records
|
||||
// the two components as a group ("template.atespace", "template.name") rather
|
||||
// than flattening them into one opaque string.
|
||||
func (r ResourceRef[R]) LogValue() slog.Value {
|
||||
return slog.GroupValue(
|
||||
slog.String("atespace", r.Atespace),
|
||||
slog.String("name", r.Name),
|
||||
)
|
||||
}
|
||||
|
||||
// ToObjectRef converts the reference to its wire form.
|
||||
func (r ResourceRef[R]) ToObjectRef() *ateapipb.ObjectRef {
|
||||
return &ateapipb.ObjectRef{Atespace: r.Atespace, Name: r.Name}
|
||||
}
|
||||
|
||||
// resourceRefFromObjectRef converts a wire reference to the in-process form.
|
||||
func resourceRefFromObjectRef[R Resource](ref *ateapipb.ObjectRef) ResourceRef[R] {
|
||||
return ResourceRef[R]{Atespace: ref.GetAtespace(), Name: ref.GetName()}
|
||||
}
|
||||
|
||||
// ActorRef identifies an actor by the (atespace, name).
|
||||
type ActorRef = ResourceRef[*ateapipb.Actor]
|
||||
|
||||
// ActorRefFromObjectRef converts a wire reference to an ActorRef.
|
||||
func ActorRefFromObjectRef(ref *ateapipb.ObjectRef) ActorRef {
|
||||
return resourceRefFromObjectRef[*ateapipb.Actor](ref)
|
||||
}
|
||||
|
||||
// ActorRefFromActor returns the reference addressing the given actor.
|
||||
func ActorRefFromActor(a *ateapipb.Actor) ActorRef {
|
||||
return ActorRef{
|
||||
Atespace: a.GetMetadata().GetAtespace(),
|
||||
Name: a.GetMetadata().GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
// ActorDNSName returns the uniform DNS name the actor is reachable at.
|
||||
// This is: "<name>.<atespace>.actors.resources.substrate.ate.dev".
|
||||
func ActorDNSName(r ActorRef) string {
|
||||
return r.Name + "." + r.Atespace + "." + ActorDNSSuffix
|
||||
}
|
||||
|
||||
// ParseActorDNSName parses a DNS name for a given actor.
|
||||
func ParseActorDNSName(name string) (ActorRef, error) {
|
||||
rest, found := strings.CutSuffix(strings.TrimSuffix(name, "."), "."+ActorDNSSuffix)
|
||||
if !found {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name: must end with %s, got %q", ActorDNSSuffix, name)
|
||||
}
|
||||
actorName, atespace, found := strings.Cut(rest, ".")
|
||||
if !found {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name: expected <actor_name>.<atespace>.%s, got %q", ActorDNSSuffix, name)
|
||||
}
|
||||
if !IsValidResourceName(actorName) {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name %q: %q is not a valid actor name", name, actorName)
|
||||
}
|
||||
if !IsValidResourceName(atespace) {
|
||||
return ActorRef{}, fmt.Errorf("invalid actor DNS name %q: %q is not a valid atespace", name, atespace)
|
||||
}
|
||||
return ActorRef{Atespace: atespace, Name: actorName}, nil
|
||||
}
|
||||
|
||||
// ActorTemplateRef identifies an ActorTemplate by the (atespace, name).
|
||||
type ActorTemplateRef = ResourceRef[*ateapipb.ActorTemplate]
|
||||
|
||||
// ActorTemplateRefFromObjectRef converts a wire reference to an ActorTemplateRef.
|
||||
func ActorTemplateRefFromObjectRef(ref *ateapipb.ObjectRef) ActorTemplateRef {
|
||||
return resourceRefFromObjectRef[*ateapipb.ActorTemplate](ref)
|
||||
}
|
||||
|
||||
// ActorTemplateRefFromActorTemplate returns the reference addressing the given
|
||||
// template.
|
||||
func ActorTemplateRefFromActorTemplate(t *ateapipb.ActorTemplate) ActorTemplateRef {
|
||||
return ActorTemplateRef{
|
||||
Atespace: t.GetMetadata().GetAtespace(),
|
||||
Name: t.GetMetadata().GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
// ActorTemplateVersionRef identifies an ActorTemplateVersion by the
|
||||
// (atespace, name).
|
||||
type ActorTemplateVersionRef = ResourceRef[*ateapipb.ActorTemplateVersion]
|
||||
|
||||
// ActorTemplateVersionRefFromObjectRef converts a wire reference to an
|
||||
// ActorTemplateVersionRef.
|
||||
func ActorTemplateVersionRefFromObjectRef(ref *ateapipb.ObjectRef) ActorTemplateVersionRef {
|
||||
return resourceRefFromObjectRef[*ateapipb.ActorTemplateVersion](ref)
|
||||
}
|
||||
|
||||
// ActorTemplateVersionRefFromActorTemplateVersion returns the reference
|
||||
// addressing the given version.
|
||||
func ActorTemplateVersionRefFromActorTemplateVersion(v *ateapipb.ActorTemplateVersion) ActorTemplateVersionRef {
|
||||
return ActorTemplateVersionRef{
|
||||
Atespace: v.GetMetadata().GetAtespace(),
|
||||
Name: v.GetMetadata().GetName(),
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,122 @@
|
||||
package resources
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
|
||||
)
|
||||
|
||||
// The ref aliases must stay distinct types: the phantom ResourceRef kind is
|
||||
// what stops a reference to one resource kind from being passed where another
|
||||
// is expected.
|
||||
func TestRefAliasesAreDistinctTypes(t *testing.T) {
|
||||
types := map[string]reflect.Type{
|
||||
"ActorRef": reflect.TypeFor[ActorRef](),
|
||||
"ActorTemplateRef": reflect.TypeFor[ActorTemplateRef](),
|
||||
"ActorTemplateVersionRef": reflect.TypeFor[ActorTemplateVersionRef](),
|
||||
}
|
||||
seen := make(map[reflect.Type]string)
|
||||
for name, typ := range types {
|
||||
if other, dup := seen[typ]; dup {
|
||||
t.Errorf("%s and %s are the same type; the phantom kind marker was lost", name, other)
|
||||
}
|
||||
seen[typ] = name
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorRefString(t *testing.T) {
|
||||
got := ActorRef{Atespace: "team-a", Name: "act-1"}.String()
|
||||
if want := "team-a/act-1"; got != want {
|
||||
t.Errorf("String() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorRefDNSName(t *testing.T) {
|
||||
actorRef := ActorRef{Atespace: "team-a", Name: "act-1"}
|
||||
|
||||
got := ActorDNSName(actorRef)
|
||||
want := "act-1.team-a.actors.resources.substrate.ate.dev"
|
||||
if got != want {
|
||||
t.Errorf("ActorDNSName() = %q, want %q", got, want)
|
||||
}
|
||||
|
||||
parsed, err := ParseActorDNSName(got)
|
||||
if err != nil {
|
||||
t.Fatalf("ParseActorDNSName(%q) error = %v", got, err)
|
||||
}
|
||||
if parsed != actorRef {
|
||||
t.Errorf("round-trip = %+v, want %+v", parsed, actorRef)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseActorDNSName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want ActorRef
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid", "act-1.team-a.actors.resources.substrate.ate.dev", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
|
||||
{"valid trailing dot", "act-1.team-a.actors.resources.substrate.ate.dev.", ActorRef{Atespace: "team-a", Name: "act-1"}, false},
|
||||
{"wrong suffix", "act-1.team-a.example.com", ActorRef{}, true},
|
||||
{"missing atespace", "act-1.actors.resources.substrate.ate.dev", ActorRef{}, true},
|
||||
{"invalid actor name", "ACT-1.team-a.actors.resources.substrate.ate.dev", ActorRef{}, true},
|
||||
{"invalid atespace", "act-1.TEAM.actors.resources.substrate.ate.dev", ActorRef{}, true},
|
||||
{"host:port not accepted", "act-1.team-a.actors.resources.substrate.ate.dev:8080", ActorRef{}, true},
|
||||
{"empty", "", ActorRef{}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseActorDNSName(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Fatalf("ParseActorDNSName(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("ParseActorDNSName(%q) = %+v, want %+v", tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorRefObjectRefRoundTrip(t *testing.T) {
|
||||
actorRef := ActorRef{Atespace: "team-a", Name: "act-1"}
|
||||
|
||||
obj := actorRef.ToObjectRef()
|
||||
if obj.GetAtespace() != "team-a" || obj.GetName() != "act-1" {
|
||||
t.Errorf("ToObjectRef() = (%q, %q), want (team-a, act-1)", obj.GetAtespace(), obj.GetName())
|
||||
}
|
||||
if got := ActorRefFromObjectRef(obj); got != actorRef {
|
||||
t.Errorf("round-trip = %+v, want %+v", got, actorRef)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorRefFromActor(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
actor *ateapipb.Actor
|
||||
want ActorRef
|
||||
}{
|
||||
{
|
||||
name: "populated",
|
||||
actor: &ateapipb.Actor{Metadata: &ateapipb.ResourceMetadata{
|
||||
Atespace: "team-a",
|
||||
Name: "act-1",
|
||||
}},
|
||||
want: ActorRef{Atespace: "team-a", Name: "act-1"},
|
||||
},
|
||||
{"nil actor", nil, ActorRef{}},
|
||||
{"nil metadata", &ateapipb.Actor{}, ActorRef{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := ActorRefFromActor(tt.actor); got != tt.want {
|
||||
t.Errorf("ActorRefFromActor() = %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestActorTemplateRefString(t *testing.T) {
|
||||
got := ActorTemplateRef{Atespace: "team-a", Name: "tmpl-1"}.String()
|
||||
if want := "team-a/tmpl-1"; got != want {
|
||||
Reference in New Issue
Block a user