mirror of
https://github.com/MichaelMure/git-bug.git
synced 2024-12-13 12:05:02 +03:00
dag: test op serialisation with the unmarshaller, to allow resolving entities
This commit is contained in:
parent
ccb71fea57
commit
e1b172aaf0
@ -23,7 +23,7 @@ const formatVersion = 4
|
||||
var def = dag.Definition{
|
||||
Typename: "bug",
|
||||
Namespace: "bugs",
|
||||
OperationUnmarshaler: operationUnmarshaller,
|
||||
OperationUnmarshaler: operationUnmarshaler,
|
||||
FormatVersion: formatVersion,
|
||||
}
|
||||
|
||||
|
@ -4,15 +4,16 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/MichaelMure/git-bug/entities/identity"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/entity/dag"
|
||||
"github.com/MichaelMure/git-bug/repository"
|
||||
)
|
||||
|
||||
func TestAddCommentSerialize(t *testing.T) {
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation {
|
||||
return NewAddCommentOp(author, unixTime, "message", nil)
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
|
||||
return NewAddCommentOp(author, unixTime, "message", nil), nil
|
||||
})
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *AddCommentOperation {
|
||||
return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"})
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*AddCommentOperation, entity.Resolvers) {
|
||||
return NewAddCommentOp(author, unixTime, "message", []repository.Hash{"hash1", "hash2"}), nil
|
||||
})
|
||||
}
|
||||
|
@ -40,10 +40,10 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCreateSerialize(t *testing.T) {
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation {
|
||||
return NewCreateOp(author, unixTime, "title", "message", nil)
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) {
|
||||
return NewCreateOp(author, unixTime, "title", "message", nil), nil
|
||||
})
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *CreateOperation {
|
||||
return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"})
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*CreateOperation, entity.Resolvers) {
|
||||
return NewCreateOp(author, unixTime, "title", "message", []repository.Hash{"hash1", "hash2"}), nil
|
||||
})
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/MichaelMure/git-bug/entities/identity"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/entity/dag"
|
||||
"github.com/MichaelMure/git-bug/repository"
|
||||
)
|
||||
@ -75,10 +76,10 @@ func TestEdit(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEditCommentSerialize(t *testing.T) {
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation {
|
||||
return NewEditCommentOp(author, unixTime, "target", "message", nil)
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
|
||||
return NewEditCommentOp(author, unixTime, "target", "message", nil), nil
|
||||
})
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *EditCommentOperation {
|
||||
return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"})
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*EditCommentOperation, entity.Resolvers) {
|
||||
return NewEditCommentOp(author, unixTime, "target", "message", []repository.Hash{"hash1", "hash2"}), nil
|
||||
})
|
||||
}
|
||||
|
@ -4,17 +4,18 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/MichaelMure/git-bug/entities/identity"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/entity/dag"
|
||||
)
|
||||
|
||||
func TestLabelChangeSerialize(t *testing.T) {
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
|
||||
return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"})
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
|
||||
return NewLabelChangeOperation(author, unixTime, []Label{"added"}, []Label{"removed"}), nil
|
||||
})
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
|
||||
return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil)
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
|
||||
return NewLabelChangeOperation(author, unixTime, []Label{"added"}, nil), nil
|
||||
})
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *LabelChangeOperation {
|
||||
return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"})
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*LabelChangeOperation, entity.Resolvers) {
|
||||
return NewLabelChangeOperation(author, unixTime, nil, []Label{"removed"}), nil
|
||||
})
|
||||
}
|
||||
|
@ -5,11 +5,12 @@ import (
|
||||
|
||||
"github.com/MichaelMure/git-bug/entities/common"
|
||||
"github.com/MichaelMure/git-bug/entities/identity"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/entity/dag"
|
||||
)
|
||||
|
||||
func TestSetStatusSerialize(t *testing.T) {
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetStatusOperation {
|
||||
return NewSetStatusOp(author, unixTime, common.ClosedStatus)
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetStatusOperation, entity.Resolvers) {
|
||||
return NewSetStatusOp(author, unixTime, common.ClosedStatus), nil
|
||||
})
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/MichaelMure/git-bug/entities/identity"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/entity/dag"
|
||||
)
|
||||
|
||||
func TestSetTitleSerialize(t *testing.T) {
|
||||
dag.SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetTitleOperation {
|
||||
return NewSetTitleOp(author, unixTime, "title", "was")
|
||||
dag.SerializeRoundTripTest(t, operationUnmarshaler, func(author identity.Interface, unixTime int64) (*SetTitleOperation, entity.Resolvers) {
|
||||
return NewSetTitleOp(author, unixTime, "title", "was"), nil
|
||||
})
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ type Operation interface {
|
||||
var _ Operation = &dag.NoOpOperation[*Snapshot]{}
|
||||
var _ Operation = &dag.SetMetadataOperation[*Snapshot]{}
|
||||
|
||||
func operationUnmarshaller(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) {
|
||||
func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) {
|
||||
var t struct {
|
||||
OperationType dag.OperationType `json:"type"`
|
||||
}
|
||||
|
@ -141,16 +141,7 @@ func makeTestContextInternal(repo repository.ClockedRepo) (identity.Interface, i
|
||||
}
|
||||
|
||||
resolvers := entity.Resolvers{
|
||||
&identity.Identity{}: entity.ResolverFunc(func(id entity.Id) (entity.Interface, error) {
|
||||
switch id {
|
||||
case id1.Id():
|
||||
return id1, nil
|
||||
case id2.Id():
|
||||
return id2, nil
|
||||
default:
|
||||
return nil, identity.ErrIdentityNotExist
|
||||
}
|
||||
}),
|
||||
&identity.Identity{}: entity.MakeResolver(id1, id2),
|
||||
}
|
||||
|
||||
def := Definition{
|
||||
|
@ -19,6 +19,8 @@ const refsPattern = "refs/%s/%s"
|
||||
const creationClockPattern = "%s-create"
|
||||
const editClockPattern = "%s-edit"
|
||||
|
||||
type OperationUnmarshaler func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error)
|
||||
|
||||
// Definition hold the details defining one specialization of an Entity.
|
||||
type Definition struct {
|
||||
// the name of the entity (bug, pull-request, ...), for human consumption
|
||||
@ -26,7 +28,7 @@ type Definition struct {
|
||||
// the Namespace in git references (bugs, prs, ...)
|
||||
Namespace string
|
||||
// a function decoding a JSON message into an Operation
|
||||
OperationUnmarshaler func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error)
|
||||
OperationUnmarshaler OperationUnmarshaler
|
||||
// the expected format version number, that can be used for data migration/upgrade
|
||||
FormatVersion uint
|
||||
}
|
||||
|
@ -208,13 +208,13 @@ func NewProjectConfig() *ProjectConfig {
|
||||
var def = dag.Definition{
|
||||
Typename: "project config",
|
||||
Namespace: "conf",
|
||||
OperationUnmarshaler: operationUnmarshaller,
|
||||
OperationUnmarshaler: operationUnmarshaler,
|
||||
FormatVersion: 1,
|
||||
}
|
||||
|
||||
// operationUnmarshaller is a function doing the de-serialization of the JSON data into our own
|
||||
// operationUnmarshaler is a function doing the de-serialization of the JSON data into our own
|
||||
// concrete Operations. If needed, we can use the resolver to connect to other entities.
|
||||
func operationUnmarshaller(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) {
|
||||
func operationUnmarshaler(raw json.RawMessage, resolvers entity.Resolvers) (dag.Operation, error) {
|
||||
var t struct {
|
||||
OperationType dag.OperationType `json:"type"`
|
||||
}
|
||||
|
@ -1,13 +1,19 @@
|
||||
package dag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/MichaelMure/git-bug/entities/identity"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
)
|
||||
|
||||
func TestNoopSerialize(t *testing.T) {
|
||||
SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *NoOpOperation[*snapshotMock] {
|
||||
return NewNoOpOp[*snapshotMock](1, author, unixTime)
|
||||
SerializeRoundTripTest(t, func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error) {
|
||||
var op NoOpOperation[*snapshotMock]
|
||||
err := json.Unmarshal(raw, &op)
|
||||
return &op, err
|
||||
}, func(author identity.Interface, unixTime int64) (*NoOpOperation[*snapshotMock], entity.Resolvers) {
|
||||
return NewNoOpOp[*snapshotMock](1, author, unixTime), nil
|
||||
})
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
package dag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/MichaelMure/git-bug/entities/identity"
|
||||
"github.com/MichaelMure/git-bug/entity"
|
||||
"github.com/MichaelMure/git-bug/repository"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -97,10 +99,14 @@ func TestSetMetadata(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSetMetadataSerialize(t *testing.T) {
|
||||
SerializeRoundTripTest(t, func(author identity.Interface, unixTime int64) *SetMetadataOperation[*snapshotMock] {
|
||||
SerializeRoundTripTest(t, func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error) {
|
||||
var op SetMetadataOperation[*snapshotMock]
|
||||
err := json.Unmarshal(raw, &op)
|
||||
return &op, err
|
||||
}, func(author identity.Interface, unixTime int64) (*SetMetadataOperation[*snapshotMock], entity.Resolvers) {
|
||||
return NewSetMetadataOp[*snapshotMock](1, author, unixTime, "message", map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
})
|
||||
}), nil
|
||||
})
|
||||
}
|
||||
|
@ -14,44 +14,30 @@ import (
|
||||
|
||||
// SerializeRoundTripTest realize a marshall/unmarshall round-trip in the same condition as with OperationPack,
|
||||
// and check if the recovered operation is identical.
|
||||
func SerializeRoundTripTest[OpT Operation](t *testing.T, maker func(author identity.Interface, unixTime int64) OpT) {
|
||||
func SerializeRoundTripTest[OpT Operation](
|
||||
t *testing.T,
|
||||
unmarshaler OperationUnmarshaler,
|
||||
maker func(author identity.Interface, unixTime int64) (OpT, entity.Resolvers),
|
||||
) {
|
||||
repo := repository.NewMockRepo()
|
||||
|
||||
rene, err := identity.NewIdentity(repo, "René Descartes", "rene@descartes.fr")
|
||||
require.NoError(t, err)
|
||||
|
||||
op := maker(rene, time.Now().Unix())
|
||||
op, resolvers := maker(rene, time.Now().Unix())
|
||||
// enforce having an id
|
||||
op.Id()
|
||||
|
||||
rdt := &roundTripper[OpT]{Before: op, author: rene}
|
||||
|
||||
data, err := json.Marshal(rdt)
|
||||
data, err := json.Marshal(op)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = json.Unmarshal(data, &rdt)
|
||||
after, err := unmarshaler(data, resolvers)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, op, rdt.after)
|
||||
}
|
||||
|
||||
type roundTripper[OpT Operation] struct {
|
||||
Before OpT
|
||||
author identity.Interface
|
||||
after OpT
|
||||
}
|
||||
|
||||
func (r *roundTripper[OpT]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(r.Before)
|
||||
}
|
||||
|
||||
func (r *roundTripper[OpT]) UnmarshalJSON(data []byte) error {
|
||||
if err := json.Unmarshal(data, &r.after); err != nil {
|
||||
return err
|
||||
}
|
||||
// Set the id from the serialized data
|
||||
r.after.setId(entity.DeriveId(data))
|
||||
after.setId(entity.DeriveId(data))
|
||||
// Set the author, as OperationPack would do
|
||||
r.after.setAuthor(r.author)
|
||||
return nil
|
||||
after.setAuthor(rene)
|
||||
|
||||
require.Equal(t, op, after)
|
||||
}
|
||||
|
@ -72,3 +72,15 @@ type ResolverFunc func(id Id) (Interface, error)
|
||||
func (fn ResolverFunc) Resolve(id Id) (Interface, error) {
|
||||
return fn(id)
|
||||
}
|
||||
|
||||
// MakeResolver create a resolver able to return the given entities.
|
||||
func MakeResolver(entities ...Interface) Resolver {
|
||||
return ResolverFunc(func(id Id) (Interface, error) {
|
||||
for _, entity := range entities {
|
||||
if entity.Id() == id {
|
||||
return entity, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("entity not found")
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user