micro--go-micro
c7657f73f4
goreleaser / goreleaser (push) Has been cancelled
* test(harness): read agent plan from the scoped store
The store-scoping change moved an agent's plan from the default table
key agent/{name}/plan to its own table (database "agent", table {name},
key "plan"). The plan-delegate harness tests still read the old key and
failed with 'not found'; read through store.Scope(mem, "agent", name)
like the agent does.
* docs: orient agents-first across README, landing, and docs overview
Lead with agents (then services and flows), surface MCP + A2A as the
interop story, and frame agents as services. Landing hero and feature
grid reordered agents-first with an A2A gateway card.
* v6: module path go-micro.dev/v6, TLS secure by default, NewService
Cut v6. Three breaking changes, bundled so the major bump is paid once:
- Module path go-micro.dev/v5 -> go-micro.dev/v6 across all imports + go.mod.
- TLS verification on by default (was off). MICRO_TLS_SECURE removed;
MICRO_TLS_INSECURE=true opts out for self-signed/dev.
- micro.NewService(name, opts...) is the canonical service constructor,
symmetric with NewAgent/NewFlow; micro.New kept as a deprecated alias;
the old name-less NewService(opts...) removed. Generators emit NewService.
Also ports the JWT auth token provider in-module (go-micro.dev/v6/auth/jwt/token
on golang-jwt/jwt/v5), dropping the v5-pinned github.com/micro/plugins/v5/auth/jwt
and the deprecated dgrijalva/jwt-go.
Docs/README/landing updated to v6 and @latest; v5->v6 migration guide added;
CHANGELOG cut as [6.0.0]. Blog posts left at their historical versions.
---------
Co-authored-by: Claude <noreply@anthropic.com>
454 行
9.0 KiB
Go
454 行
9.0 KiB
Go
package util
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"go-micro.dev/v6/metadata"
|
|
goregistry "go-micro.dev/v6/registry"
|
|
)
|
|
|
|
type parseCase struct {
|
|
args []string
|
|
values *goregistry.Value
|
|
expected map[string]interface{}
|
|
}
|
|
|
|
func TestDynamicFlagParsing(t *testing.T) {
|
|
cases := []parseCase{
|
|
{
|
|
args: []string{"--ss=a,b"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "ss",
|
|
Type: "[]string",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"ss": []interface{}{"a", "b"},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--ss", "a,b"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "ss",
|
|
Type: "[]string",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"ss": []interface{}{"a", "b"},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--ss=a", "--ss=b"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "ss",
|
|
Type: "[]string",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"ss": []interface{}{"a", "b"},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--ss", "a", "--ss", "b"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "ss",
|
|
Type: "[]string",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"ss": []interface{}{"a", "b"},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--bs=true,false"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "bs",
|
|
Type: "[]bool",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"bs": []interface{}{true, false},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--bs", "true,false"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "bs",
|
|
Type: "[]bool",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"bs": []interface{}{true, false},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--bs=true", "--bs=false"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "bs",
|
|
Type: "[]bool",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"bs": []interface{}{true, false},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--bs", "true", "--bs", "false"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "bs",
|
|
Type: "[]bool",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"bs": []interface{}{true, false},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is=10,20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int32",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int32(10), int32(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is", "10,20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int32",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int32(10), int32(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is=10", "--is=20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int32",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int32(10), int32(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is", "10", "--is", "20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int32",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int32(10), int32(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is=10,20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int64(10), int64(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is", "10,20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int64(10), int64(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is=10", "--is=20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int64(10), int64(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--is", "10", "--is", "20"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "is",
|
|
Type: "[]int64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"is": []interface{}{int64(10), int64(20)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--fs=10.1,20.2"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "fs",
|
|
Type: "[]float64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--fs", "10.1,20.2"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "fs",
|
|
Type: "[]float64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--fs=10.1", "--fs=20.2"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "fs",
|
|
Type: "[]float64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--fs", "10.1", "--fs", "20.2"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "fs",
|
|
Type: "[]float64",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"fs": []interface{}{float64(10.1), float64(20.2)},
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--user_email=someemail"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "user_email",
|
|
Type: "string",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"user_email": "someemail",
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--user_email=someemail", "--user_name=somename"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "user_email",
|
|
Type: "string",
|
|
},
|
|
{
|
|
Name: "user_name",
|
|
Type: "string",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"user_email": "someemail",
|
|
"user_name": "somename",
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--b"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "b",
|
|
Type: "bool",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"b": true,
|
|
},
|
|
},
|
|
{
|
|
args: []string{"--user_friend_email=hi"},
|
|
values: &goregistry.Value{
|
|
Values: []*goregistry.Value{
|
|
{
|
|
Name: "user_friend_email",
|
|
Type: "string",
|
|
},
|
|
},
|
|
},
|
|
expected: map[string]interface{}{
|
|
"user_friend_email": "hi",
|
|
},
|
|
},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(strings.Join(c.args, " "), func(t *testing.T) {
|
|
_, flags, err := splitCmdArgs(c.args)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
req, err := FlagsToRequest(flags, c.values)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(c.expected, req) {
|
|
spew.Dump("Expected:", c.expected, "got: ", req)
|
|
t.Fatalf("Expected %v, got %v", c.expected, req)
|
|
}
|
|
})
|
|
|
|
}
|
|
}
|
|
|
|
func TestAddMetadataToContext(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
metadataStrs []string
|
|
expectedKeys []string
|
|
expectedValues []string
|
|
}{
|
|
{
|
|
name: "Single metadata",
|
|
metadataStrs: []string{"Key1:Value1"},
|
|
expectedKeys: []string{"Key1"},
|
|
expectedValues: []string{"Value1"},
|
|
},
|
|
{
|
|
name: "Multiple metadata",
|
|
metadataStrs: []string{"Key1:Value1", "Key2:Value2"},
|
|
expectedKeys: []string{"Key1", "Key2"},
|
|
expectedValues: []string{"Value1", "Value2"},
|
|
},
|
|
{
|
|
name: "Metadata with spaces",
|
|
metadataStrs: []string{"Key1: Value1 ", " Key2 : Value2"},
|
|
expectedKeys: []string{"Key1", "Key2"},
|
|
expectedValues: []string{"Value1", "Value2"},
|
|
},
|
|
{
|
|
name: "Metadata with colon in value",
|
|
metadataStrs: []string{"Authorization:Bearer token:123"},
|
|
expectedKeys: []string{"Authorization"},
|
|
expectedValues: []string{"Bearer token:123"},
|
|
},
|
|
{
|
|
name: "Empty metadata",
|
|
metadataStrs: []string{},
|
|
expectedKeys: []string{},
|
|
expectedValues: []string{},
|
|
},
|
|
{
|
|
name: "Invalid metadata format",
|
|
metadataStrs: []string{"InvalidFormat"},
|
|
expectedKeys: []string{},
|
|
expectedValues: []string{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := context.Background()
|
|
ctx = AddMetadataToContext(ctx, tt.metadataStrs)
|
|
|
|
md, ok := metadata.FromContext(ctx)
|
|
if len(tt.expectedKeys) == 0 && !ok {
|
|
return // Expected no metadata
|
|
}
|
|
|
|
if !ok && len(tt.expectedKeys) > 0 {
|
|
t.Fatal("Expected metadata in context but got none")
|
|
}
|
|
|
|
for i, key := range tt.expectedKeys {
|
|
value, found := md.Get(key)
|
|
if !found {
|
|
t.Fatalf("Expected key %s not found in metadata", key)
|
|
}
|
|
if value != tt.expectedValues[i] {
|
|
t.Fatalf("Expected value %s for key %s, got %s", tt.expectedValues[i], key, value)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|