mukan-ignite/ignite/pkg/protoanalysis/protoutil/cursor_test.go
Mukan Erkin Törük 26b204bd04
Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
feat: fork Ignite CLI v29 as Mukan Ignite — remove cosmos-sdk restrictions
2026-05-11 03:31:37 +03:00

563 lines
13 KiB
Go

package protoutil
import (
"testing"
"github.com/emicklei/proto"
"github.com/stretchr/testify/require"
)
const (
world = "World"
elements = "Elements"
kirby = "Kirby"
)
// Make a simple replacement of package -> import.
func TestSimpleReplacement(t *testing.T) {
f, err := parseStringProto(`package "package"`)
require.NoError(t, err)
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if _, ok := n.(*proto.Package); ok {
imp := NewImport("that")
c.Replace(imp)
}
return true
})
require.True(t, containsElement(f, NewImport("that")))
require.False(t, containsElement(f, NewPackage("package")))
}
func TestSimpleInsertAfter(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
message Hello {
message World {}
}
`)
require.NoError(t, err)
// keep ref for checking containment.
var msg *proto.Message
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if n, ok := n.(*proto.Message); ok {
if n.Name == world {
msg = NewMessage("WeComeInPeace")
c.InsertAfter(msg)
}
}
return true
})
require.True(t, containsElement(f, msg))
// check that it is inserted after "World"
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if n, ok := n.(*proto.Message); ok {
if n.Name == world {
next, ok := c.Next()
require.True(t, ok)
require.True(t, next.(*proto.Message).Name == "WeComeInPeace")
}
}
return true
})
}
// Can really only panic with comments since
// other elements in nodes aren't Visitees.
//
//nolint:dupword
func TestInsertAfterPanic(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
// my import
import "this";
`)
require.NoError(t, err)
// Try calling insertAfter when c is a Comment
require.Panics(t, func() {
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if _, ok := n.(*proto.Comment); ok {
c.InsertAfter(NewImport("that"))
}
return true
})
})
}
func TestSimpleInsertBefore(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
message Say {}
message World {}
`)
require.NoError(t, err)
// keep ref for checking containment.
var msg *proto.Message
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if n, ok := n.(*proto.Message); ok {
if n.Name == world {
// add hello between say and world
msg = NewMessage("Hello")
c.InsertBefore(msg)
}
}
return true
})
require.True(t, containsElement(f, msg))
// check that it is inserted after "Say"
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if n, ok := n.(*proto.Message); ok {
if n.Name == "Say" {
next, ok := c.Next()
require.True(t, ok)
require.True(t, next.(*proto.Message).Name == "Hello")
}
}
return true
})
}
// Can really only panic with comments since
// other elements in nodes aren't Visitees.
//
//nolint:dupword
func TestInsertBeforePanic(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
// my import
import "this";
`)
require.NoError(t, err)
// Try calling insertAfter when c is a Comment
require.Panics(t, func() {
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if _, ok := n.(*proto.Comment); ok {
c.InsertBefore(NewImport("that"))
}
return true
})
})
}
// Build a skeleton of a file by continuous appends on the file.
func TestAppendFile(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"`)
require.NoError(t, err)
i := NewImport("importpath")
Append(f, i)
require.True(t, containsElement(f, i))
p := NewPackage("package")
Append(f, p)
require.True(t, containsElement(f, p))
o := NewOption("this", "that")
Append(f, o)
require.True(t, containsElement(f, o))
oneofF := NewOneofField("this", "string", 2)
// Can directly append an option if required:
opt := NewOption("this", "that")
Append(oneofF, opt)
require.True(t, containsElement(oneofF, opt))
oneof := NewOneof("myoneof")
Append(oneof, oneofF)
require.True(t, containsElement(oneof, oneofF))
normalfield := NewField("that", "string", 3)
m := NewMessage("Hello")
Append(m, oneof)
require.True(t, containsElement(m, oneof))
Append(m, normalfield)
require.True(t, containsElement(m, normalfield))
Append(f, m)
require.True(t, containsElement(f, m))
// Append an empty service
s := NewService("Hey")
Append(f, s)
require.True(t, containsElement(f, s))
// An empty enum
e := NewEnum("Hey")
// Add an enum field to it:
ef := NewEnumField("HEY", 1)
Append(e, ef)
require.True(t, containsElement(e, ef))
Append(f, e)
require.True(t, containsElement(f, e))
}
// Append to a node w/o elements panics.
func TestAppendEdges(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"`)
require.NoError(t, err)
// Can't append to a Syntax node, panic.
require.Panics(t, func() {
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if n, ok := n.(*proto.Syntax); ok {
Append(n, NewImport("that"))
}
return true
})
})
// Empty append does nothing.
elems := len(f.Elements)
Append(f)
require.True(t, len(f.Elements) == elems)
// Appending a non-option to NormalField/OneOfField panics.
require.Panics(t, func() {
f := NewField("that", "string", 3)
Append(f, NewImport("that"))
})
}
func TestCursorOps(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
message Hello {}
message World {
message Hey {}
enum E {}
}
`)
require.NoError(t, err)
Apply(f, nil, func(c *Cursor) bool {
n := c.Node()
if n, ok := n.(*proto.Message); ok {
if n.Name == "Hello" {
require.False(t, c.IsLast())
n, ok := c.Next()
require.True(t, ok)
require.NotNil(t, n)
parent, ok := c.Parent().(*proto.Proto)
require.True(t, ok)
require.True(t, parent.Filename == "")
// currently useless.
require.True(t, c.Name() == elements)
}
if n.Name == world {
require.True(t, c.IsLast())
n, ok := c.Next()
require.False(t, ok)
require.Nil(t, n)
parent, ok := c.Parent().(*proto.Proto)
require.True(t, ok)
require.True(t, parent.Filename == "")
// currently useless.
require.True(t, c.Name() == elements)
}
if n.Name == "Hey" {
require.False(t, c.IsLast())
n, ok := c.Next()
require.True(t, ok)
require.NotNil(t, n)
// parent is the message
parent, ok := c.Parent().(*proto.Message)
require.True(t, ok)
require.True(t, parent.Name == "World")
// currently useless.
require.True(t, c.Name() == elements)
}
}
if _, ok := n.(*proto.Enum); ok {
require.True(t, c.IsLast())
n, ok := c.Next()
require.False(t, ok)
require.Nil(t, n)
// parent is the message
parent, ok := c.Parent().(*proto.Message)
require.True(t, ok)
require.True(t, parent.Name == "World")
// currently useless.
require.True(t, c.Name() == elements)
}
// Don't make sense for elements not contained in a slice (currently
// proto.Proto or comments)
if _, ok := n.(*proto.Proto); ok {
require.Panics(t, func() { c.IsLast() })
require.Panics(t, func() { c.Next() })
}
return true
})
}
// Also test the utilities here.
func TestAddImports(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"`)
require.NoError(t, err)
// Add an import
err = AddImports(f, true, NewImport("this.proto"))
require.NoError(t, err)
require.True(t, HasImport(f, "this.proto"))
// Note: added in reverse order.
err = AddImports(f, true,
NewImport("that.proto"),
NewImport("the.other.proto"),
NewImport("and.another.proto"),
)
require.NoError(t, err)
require.True(t, HasImport(f, "that.proto"))
require.True(t, HasImport(f, "the.other.proto"))
require.True(t, HasImport(f, "and.another.proto"))
// Empty import is no-op.
require.NoError(t, AddImports(f, true))
// Importing on empty file is currently an error.
require.Error(t, AddImports(
&proto.Proto{},
true,
NewImport("this.proto"),
))
// Exercise the recursive case:
f, err = parseStringProto(`syntax = "proto3"`)
require.NoError(t, err)
err = AddImports(f, true,
NewImport("this.proto"),
NewImport("that.proto"),
)
require.NoError(t, err)
require.True(t, HasImport(f, "this.proto"))
require.True(t, HasImport(f, "that.proto"))
f, err = parseStringProto(`syntax = "proto3";
package cosmonaut.chainname.chainname;
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "chainname/params.proto";
`)
require.NoError(t, err)
err = AddImports(f, true, NewImport("chainname/bleep.proto"))
require.NoError(t, err)
// Add dupes:
err = AddImports(f, true, NewImport("chainname/bleep.proto"))
require.NoError(t, err)
err = AddImports(f, true, NewImport("chainname/bleep.proto"))
require.NoError(t, err)
err = AddImports(f, true, NewImport("chainname/params.proto"))
require.NoError(t, err)
// just checking that is added last.
// fmt.Print(Printer(f))
// Check that adding duplicates does nothing.
f, err = parseStringProto(`syntax = "proto3";
package cosmonaut.chainname.chainname;
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "chainname/params.proto";
`)
require.NoError(t, err)
imports := []*proto.Import{
NewImport("chainname/params.proto"),
NewImport("gogoproto/gogo.proto"),
}
err = AddImports(f, true, imports...)
require.NoError(t, err)
require.Equal(t, len(f.Elements), 6, "The number of elements shouldn't have changed")
// Pass an empty import list.
f, err = parseStringProto(`syntax = "proto3";`)
require.NoError(t, err)
ret := AddImports(f, true)
require.Nil(t, ret)
// No imports, no fallback.
f, err = parseStringProto(`syntax = "proto3";`)
require.NoError(t, err)
err = AddImports(f, false, NewImport("this.proto"))
require.Error(t, err)
}
func TestHasImport(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
import "this.proto";
import "that.proto";
import "the.other.proto"
`)
require.NoError(t, err)
require.True(t, HasImport(f, "this.proto"))
require.True(t, HasImport(f, "that.proto"))
require.True(t, HasImport(f, "the.other.proto"))
require.False(t, HasImport(f, "this.proto.proto"))
}
func TestGetMessage(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
message Hello {
message World {
message WeComeInPeace {
message TheAnswerToLifeTheUniverseAndEverything {
message IsActuallyFortyTwo {}
}
}
}
}
`)
require.NoError(t, err)
m, err := GetMessageByName(f, "Hello")
require.NoError(t, err)
require.Equal(t, "Hello", m.Name)
m, err = GetMessageByName(f, "World")
require.NoError(t, err)
require.Equal(t, "World", m.Name)
m, err = GetMessageByName(f, "WeComeInPeace")
require.NoError(t, err)
require.Equal(t, "WeComeInPeace", m.Name)
m, err = GetMessageByName(f, "TheAnswerToLifeTheUniverseAndEverything")
require.NoError(t, err)
require.Equal(t, "TheAnswerToLifeTheUniverseAndEverything", m.Name)
m, err = GetMessageByName(f, "IsActuallyFortyTwo")
require.NoError(t, err)
require.Equal(t, "IsActuallyFortyTwo", m.Name)
_, err = GetMessageByName(f, "DoesNotExist")
require.Error(t, err)
}
func TestHasMessage(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
message Hello {
message World {
message WeComeInPeace {
message TheAnswerToLifeTheUniverseAndEverything {
message IsActuallyFortyTwo {}
}
}
}
}
`)
require.NoError(t, err)
require.True(t, HasMessage(f, "Hello"))
require.True(t, HasMessage(f, "World"))
require.True(t, HasMessage(f, "WeComeInPeace"))
require.True(t, HasMessage(f, "TheAnswerToLifeTheUniverseAndEverything"))
require.True(t, HasMessage(f, "IsActuallyFortyTwo"))
require.False(t, HasMessage(f, "DoesNotExist"))
require.False(t, HasMessage(f, "Hello.World"))
}
func TestGetService(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
service Msg {
}
service AnotherMsg {}
service YetAnotherMsg {
rpc Foo(Bar) returns (Bar) {}
}
`)
require.NoError(t, err)
s, err := GetServiceByName(f, "Msg")
require.NoError(t, err)
require.Equal(t, "Msg", s.Name)
s, err = GetServiceByName(f, "AnotherMsg")
require.NoError(t, err)
require.Equal(t, "AnotherMsg", s.Name)
s, err = GetServiceByName(f, "YetAnotherMsg")
require.NoError(t, err)
require.Equal(t, "YetAnotherMsg", s.Name)
_, err = GetServiceByName(f, "DoesNotExist")
require.Error(t, err)
}
func TestHasService(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
service Msg {}
service AnotherMsg {}
service YetAnotherMsg {}
`)
require.NoError(t, err)
require.True(t, HasService(f, "Msg"))
require.True(t, HasService(f, "AnotherMsg"))
require.True(t, HasService(f, "YetAnotherMsg"))
require.False(t, HasService(f, "DoesNotExist"))
}
func TestGetNextId(t *testing.T) {
f, err := parseStringProto(`syntax = "proto3"
message Hello {
string g = 1;
message World {
message WeComeInPeace {
message TheAnswerToLifeTheUniverseAndEverything {
message IsActuallyFortyTwo {
string foo = 1;
int32 bar = 2;
int64 baz = 3;
}
}
}
}
}
`)
require.NoError(t, err)
m, err := GetMessageByName(f, "IsActuallyFortyTwo")
require.NoError(t, err)
require.Equal(t, 4, NextUniqueID(m))
m, err = GetMessageByName(f, "Hello")
require.NoError(t, err)
require.Equal(t, 2, NextUniqueID(m))
f, err = parseStringProto(`syntax = "proto3"
message Hello {
string g = 1;
string foo = 2;
int32 bar = 3;
int64 baz = 5;
}`)
require.NoError(t, err)
m, err = GetMessageByName(f, "Hello")
require.NoError(t, err)
require.Equal(t, 6, NextUniqueID(m))
}