From 8fa5485ebc93921e7e381a1ebb675f795832c68e Mon Sep 17 00:00:00 2001 From: Jason Bonafide Date: Wed, 27 May 2020 23:09:33 -0400 Subject: [PATCH] Add IOTA Tangle assesor. Signed-off-by: Jason Bonafide --- cmd/examples/multistage/main.go | 17 + internal/pkg/test/iota.go | 51 +++ internal/pkg/test/iota_test.go | 140 ++++++++ .../assess/assessor/iota/assessor.go | 125 +++++++ .../assess/assessor/iota/assessor_test.go | 326 ++++++++++++++++++ .../assess/assessor/iota/client/contract.go | 26 ++ .../assess/assessor/iota/client/stub/stub.go | 43 +++ .../assess/assessor/iota/metadata/failure.go | 36 ++ .../assessor/iota/metadata/failure_test.go | 30 ++ .../assess/assessor/iota/metadata/kind.go | 19 + .../assess/assessor/iota/metadata/success.go | 38 ++ .../assessor/iota/metadata/success_test.go | 28 ++ 12 files changed, 879 insertions(+) create mode 100644 pkg/annotator/assess/assessor/iota/assessor.go create mode 100644 pkg/annotator/assess/assessor/iota/assessor_test.go create mode 100644 pkg/annotator/assess/assessor/iota/client/contract.go create mode 100644 pkg/annotator/assess/assessor/iota/client/stub/stub.go create mode 100644 pkg/annotator/assess/assessor/iota/metadata/failure.go create mode 100644 pkg/annotator/assess/assessor/iota/metadata/failure_test.go create mode 100644 pkg/annotator/assess/assessor/iota/metadata/kind.go create mode 100644 pkg/annotator/assess/assessor/iota/metadata/success.go create mode 100644 pkg/annotator/assess/assessor/iota/metadata/success_test.go diff --git a/cmd/examples/multistage/main.go b/cmd/examples/multistage/main.go index 8a26acf..854907f 100644 --- a/cmd/examples/multistage/main.go +++ b/cmd/examples/multistage/main.go @@ -27,6 +27,7 @@ import ( "github.com/project-alvarium/go-sdk/pkg/annotation/uniqueprovider/ulid" "github.com/project-alvarium/go-sdk/pkg/annotator" "github.com/project-alvarium/go-sdk/pkg/annotator/assess" + iotaAssessor "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/iota" pkiAssessor "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/pki" "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/pki/factory/verifier" filterFactory "github.com/project-alvarium/go-sdk/pkg/annotator/filter/matching" @@ -42,6 +43,7 @@ import ( "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/example" "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/example/writer/testwriter" "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/iota" + iotaPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/iota/metadata" "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/ipfs" ipfsPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/ipfs/metadata" "github.com/project-alvarium/go-sdk/pkg/hashprovider/sha256" @@ -258,6 +260,21 @@ func main() { }, ), ), + assess.New( + p, + uniqueProvider, + idProvider, + persistence, + iotaAssessor.New(newClient(iotaURL)), + filterFactory.New( + func(annotation *annotation.Instance) bool { + t, ok := annotation.Metadata.(*publishMetadata.Instance) + return ok && + (t.PublisherKind == iotaPublisherMetadata.Kind || + t.PublisherKind == ipfsPublisherMetadata.Kind) + }, + ), + ), publish.New(p, uniqueProvider, idProvider, persistence, example.New(w), passthroughFilter), }, ) diff --git a/internal/pkg/test/iota.go b/internal/pkg/test/iota.go index 9390cb8..8fd4198 100644 --- a/internal/pkg/test/iota.go +++ b/internal/pkg/test/iota.go @@ -15,12 +15,21 @@ package test import ( + "encoding/json" "math/rand" + "testing" + "github.com/project-alvarium/go-sdk/pkg/annotation" + "github.com/project-alvarium/go-sdk/pkg/annotation/metadata" + publishMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/metadata" + "github.com/project-alvarium/go-sdk/pkg/hashprovider/sha256" + identityProvider "github.com/project-alvarium/go-sdk/pkg/identityprovider/hash" "github.com/project-alvarium/go-sdk/pkg/test" "github.com/iotaledger/iota.go/bundle" + "github.com/iotaledger/iota.go/converter" "github.com/iotaledger/iota.go/transaction" + "github.com/iotaledger/iota.go/trinary" ) const ( @@ -44,6 +53,11 @@ func FactoryRandomAddressTrytesString() string { return FactoryRandomFixedLengthTrytesString(addressSize) } +// FactoryRandomInvalidAddressTrytesString returns Trytes for an Address of a fixed invalid length with a random value. +func FactoryRandomInvalidAddressTrytesString() string { + return FactoryRandomFixedLengthTrytesString(addressSize - 1) +} + // FactoryRandomFixedSizeBundle returns a Bundle of a fixed size with random transaction values with a random length. func FactoryRandomFixedSizeBundle(size int) bundle.Bundle { length := rand.Intn(1024) @@ -57,3 +71,40 @@ func FactoryRandomFixedSizeBundle(size int) bundle.Bundle { } return txs } + +// FactoryAnnotationsTransaction returns a Transaction comprised of a slice of annotation instances. +func FactoryAnnotationsTransaction(t *testing.T, unique string, metadata metadata.Contract) transaction.Transaction { + marshalledAnnotations, _ := json.Marshal([]*annotation.Instance{ + factoryPublisherAnnotation(unique, metadata), + }) + return bytesToTransaction(marshalledAnnotations) +} + +// FactoryAnnotationTransaction returns a Transaction comprised of a single annotation instance. +func FactoryAnnotationTransaction(t *testing.T, unique string, metadata metadata.Contract) transaction.Transaction { + marshalledAnnotations, _ := json.Marshal(factoryPublisherAnnotation(unique, metadata)) + return bytesToTransaction(marshalledAnnotations) +} + +// factoryPublisherAnnotation is a factory function that returns an initialized instance of an annotation. +func factoryPublisherAnnotation(unique string, metadata metadata.Contract) *annotation.Instance { + return &annotation.Instance{ + Unique: unique, + Created: test.FactoryRandomString(), + CurrentIdentityKind: test.FactoryRandomString(), + CurrentIdentity: identityProvider.New(sha256.New()).Derive(test.FactoryRandomByteSlice()), + PreviousIdentityKind: test.FactoryRandomString(), + PreviousIdentity: nil, + MetadataKind: publishMetadata.Kind, + Metadata: metadata, + } +} + +// bytesToTransaction returns a Transaction containing a signature made up of the given byte slice. +func bytesToTransaction(data []byte) transaction.Transaction { + trytes, _ := converter.ASCIIToTrytes(string(data)) + signatureTrytes, _ := trinary.Pad(trytes, len(trytes)+9) + return transaction.Transaction{ + SignatureMessageFragment: signatureTrytes, + } +} diff --git a/internal/pkg/test/iota_test.go b/internal/pkg/test/iota_test.go index 13f6878..42cc2ad 100644 --- a/internal/pkg/test/iota_test.go +++ b/internal/pkg/test/iota_test.go @@ -15,10 +15,16 @@ package test import ( + "encoding/json" "math/rand" "strings" "testing" + "github.com/project-alvarium/go-sdk/pkg/annotation" + ipfsPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/ipfs/metadata" + "github.com/project-alvarium/go-sdk/pkg/test" + + "github.com/iotaledger/iota.go/converter" "github.com/iotaledger/iota.go/guards/validators" "github.com/iotaledger/iota.go/trinary" "github.com/stretchr/testify/assert" @@ -155,6 +161,48 @@ func TestFactoryRandomAddressTrytesString(t *testing.T) { } } +// TestFactoryRandomInvalidAddressTrytesString tests FactoryRandomInvalidAddressTrytesString. +func TestFactoryRandomInvalidAddressTrytesString(t *testing.T) { + type testCase struct { + name string + test func(t *testing.T) + } + + cases := []testCase{ + { + name: "returns fixed size", + test: func(t *testing.T) { + result := FactoryRandomInvalidAddressTrytesString() + + assert.Equal(t, addressSize-1, len(result)) + }, + }, + { + name: "returns valid charset", + test: func(t *testing.T) { + result := FactoryRandomInvalidAddressTrytesString() + + for i := range result { + assert.True(t, strings.Contains(trytesCharset, string(result[i]))) + } + }, + }, + { + name: "returns varying content", + test: func(t *testing.T) { + result1 := FactoryRandomInvalidAddressTrytesString() + result2 := FactoryRandomInvalidAddressTrytesString() + + assert.NotEqual(t, result1, result2) + }, + }, + } + + for i := range cases { + t.Run(cases[i].name, cases[i].test) + } +} + // TestFactoryRandomFixedSizeBundle tests FactoryRandomFixedSizeBundle. func TestFactoryRandomFixedSizeBundle(t *testing.T) { type testCase struct { @@ -223,3 +271,95 @@ func TestFactoryRandomFixedSizeBundle(t *testing.T) { t.Run(cases[i].name, cases[i].test) } } + +// TestFactoryAnnotationsTransaction tests FactoryAnnotationsTransaction. +func TestFactoryAnnotationsTransaction(t *testing.T) { + type testCase struct { + name string + test func(t *testing.T) + } + + cases := []testCase{ + { + name: "transaction's content varies", + test: func(t *testing.T) { + unique := test.FactoryRandomString() + ipfsMetadata := ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()) + result1 := FactoryAnnotationsTransaction(t, unique, ipfsMetadata) + result2 := FactoryAnnotationsTransaction(t, unique, ipfsMetadata) + + assert.NotEqual(t, result1, result2) + }, + }, + { + name: "transaction content contains an annotation slice", + test: func(t *testing.T) { + unique := test.FactoryRandomString() + ipfsMetadata := ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()) + result := FactoryAnnotationsTransaction(t, unique, ipfsMetadata) + + content, err := converter.TrytesToASCII(result.SignatureMessageFragment[:len(result.SignatureMessageFragment)-9]) + if err != nil { + assert.FailNow(t, "Unexpected error converting Trtyes to ASCII", err.Error()) + } + + a := []*annotation.Instance{factoryPublisherAnnotation(unique, ipfsMetadata)} + marshalledAnnotation, err := json.Marshal(a) + if err != nil { + assert.FailNow(t, "Unexpected marshal failure", err.Error()) + } + assert.IsType(t, string(marshalledAnnotation), content) + }, + }, + } + + for i := range cases { + t.Run(cases[i].name, cases[i].test) + } +} + +// TestFactoryAnnotationTransaction tests FactoryAnnotationTransaction. +func TestFactoryAnnotationTransaction(t *testing.T) { + type testCase struct { + name string + test func(t *testing.T) + } + + cases := []testCase{ + { + name: "transaction's content varies", + test: func(t *testing.T) { + unique := test.FactoryRandomString() + ipfsMetadata := ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()) + result1 := FactoryAnnotationTransaction(t, unique, ipfsMetadata) + result2 := FactoryAnnotationTransaction(t, unique, ipfsMetadata) + + assert.NotEqual(t, result1, result2) + }, + }, + { + name: "transaction content contains an annotation instance", + test: func(t *testing.T) { + unique := test.FactoryRandomString() + ipfsMetadata := ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()) + result := FactoryAnnotationTransaction(t, unique, ipfsMetadata) + + content, err := converter.TrytesToASCII(result.SignatureMessageFragment[:len(result.SignatureMessageFragment)-9]) + if err != nil { + assert.FailNow(t, "Unexpected error converting Trtyes to ASCII", err.Error()) + } + + a := factoryPublisherAnnotation(unique, ipfsMetadata) + marshalledAnnotation, err := json.Marshal(a) + if err != nil { + assert.FailNow(t, "Unexpected marshal failure", err.Error()) + } + assert.IsType(t, string(marshalledAnnotation), content) + }, + }, + } + + for i := range cases { + t.Run(cases[i].name, cases[i].test) + } +} diff --git a/pkg/annotator/assess/assessor/iota/assessor.go b/pkg/annotator/assess/assessor/iota/assessor.go new file mode 100644 index 0000000..5d50b81 --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/assessor.go @@ -0,0 +1,125 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 iota + +import ( + "github.com/project-alvarium/go-sdk/pkg/annotation" + "github.com/project-alvarium/go-sdk/pkg/annotation/metadata" + annotationMetadataFactory "github.com/project-alvarium/go-sdk/pkg/annotation/metadata/factory" + "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/iota/client" + iotaAssessorMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/iota/metadata" + publishMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/metadata" + publisherMetadataFactory "github.com/project-alvarium/go-sdk/pkg/annotator/publish/metadata/factory" + iotaPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/iota/metadata" + ipfsPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/ipfs/metadata" + identityFactory "github.com/project-alvarium/go-sdk/pkg/identity/factory" + + "github.com/iotaledger/iota.go/address" + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/transaction" + "github.com/iotaledger/iota.go/trinary" +) + +// assessor is a receiver that encapsulates required dependencies. +type assessor struct { + client client.Contract +} + +// New is a factory function that returns an initialized assessor. +func New(client client.Contract) *assessor { + return &assessor{ + client: client, + } +} + +// SetUp is called once when the assessor is instantiated. +func (*assessor) SetUp() {} + +// TearDown is called once when assessor is terminated. +func (*assessor) TearDown() {} + +// failureAnnotationMatch is called to return a failure annotation; enables unit testing. +func (*assessor) failureAnnotationMatch() metadata.Contract { + return iotaAssessorMetadata.NewFailure("IOTA annotation assessor requires IOTA and IPFS annotations") +} + +// Assess accepts data and returns associated assessments. +func (a *assessor) Assess(annotations []*annotation.Instance) metadata.Contract { + uniques := make([]string, 0) + var ipfsAnnotation *annotation.Instance + var iotaAnnotation *annotation.Instance + for i := range annotations { + switch annotations[i].Metadata.(*publishMetadata.Instance).PublisherKind { + case ipfsPublisherMetadata.Kind: + ipfsAnnotation = annotations[i] + case iotaPublisherMetadata.Kind: + iotaAnnotation = annotations[i] + uniques = append(uniques, annotations[i].Unique) + } + } + + if ipfsAnnotation == nil || iotaAnnotation == nil { + return a.failureAnnotationMatch() + } + + iotaMetadata := + iotaAnnotation.Metadata.(*publishMetadata.Instance).PublisherMetadata.(*iotaPublisherMetadata.Success) + addressChecksum, err := address.Checksum(iotaMetadata.Address) + if err != nil { + return a.Failure(err.Error()) + } + txs, err := a.client.FindTransactionObjects(api.FindTransactionsQuery{ + Addresses: []trinary.Hash{iotaMetadata.Address + addressChecksum}, + }) + if err != nil { + return a.Failure(err.Error()) + } + + txsJson, err := transaction.ExtractJSON(txs) + if err != nil { + return a.Failure(err.Error()) + } + + iotaPublisherAnnotation := annotation.Instance{} + iotaPublisherAnnotation.SetIdentityFactory(identityFactory.New()) + iotaPublisherAnnotation.SetMetadataFactory( + annotationMetadataFactory.New([]annotationMetadataFactory.Contract{publisherMetadataFactory.NewDefault()}), + ) + + err = iotaPublisherAnnotation.UnmarshalJSON([]byte(txsJson[1 : len(txsJson)-1])) + if err != nil { + return a.Failure(err.Error()) + } + + ipfsMetadata := + ipfsAnnotation.Metadata.(*publishMetadata.Instance).PublisherMetadata.(*ipfsPublisherMetadata.Success) + iotaIpfsMetadata := + iotaPublisherAnnotation.Metadata.(*publishMetadata.Instance).PublisherMetadata.(*ipfsPublisherMetadata.Success) + + if *iotaIpfsMetadata != *ipfsMetadata { + return iotaAssessorMetadata.NewSuccess(false, uniques) + } + return iotaAssessorMetadata.NewSuccess(true, uniques) +} + +// Failure creates a publisher-specific failure annotation. +func (*assessor) Failure(errorMessage string) metadata.Contract { + return iotaAssessorMetadata.NewFailure(errorMessage) +} + +// Kind returns an implementation mnemonic +func (*assessor) Kind() string { + return iotaPublisherMetadata.Kind +} diff --git a/pkg/annotator/assess/assessor/iota/assessor_test.go b/pkg/annotator/assess/assessor/iota/assessor_test.go new file mode 100644 index 0000000..c34772d --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/assessor_test.go @@ -0,0 +1,326 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 iota + +import ( + "errors" + "fmt" + "testing" + + internalTest "github.com/project-alvarium/go-sdk/internal/pkg/test" + "github.com/project-alvarium/go-sdk/pkg/annotation" + "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/iota/client" + "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/iota/client/stub" + "github.com/project-alvarium/go-sdk/pkg/annotator/assess/assessor/iota/metadata" + publishMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/metadata" + iotaPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/iota/metadata" + ipfsPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/ipfs/metadata" + "github.com/project-alvarium/go-sdk/pkg/hashprovider/sha256" + identityProvider "github.com/project-alvarium/go-sdk/pkg/identityprovider/hash" + "github.com/project-alvarium/go-sdk/pkg/test" + + "github.com/iotaledger/iota.go/transaction" + "github.com/stretchr/testify/assert" +) + +// TestAssessor_SetUp tests assessor.SetUp. +func TestAssessor_SetUp(t *testing.T) { + sut := New(stub.New(transaction.Transactions{}, nil)) + + sut.SetUp() +} + +// TestAssessor_TearDown tests assessor.TearDown. +func TestAssessor_TearDown(t *testing.T) { + sut := New(stub.New(transaction.Transactions{}, nil)) + + sut.TearDown() + +} + +// TestAssessor_Assess tests assessor.Assess. +func TestAssessor_Assess(t *testing.T) { + type testCase struct { + name string + address string + client client.Contract + unique string + publisherMetadata []*publishMetadata.Instance + expectedResult func(sut *assessor) interface{} + } + + cases := []testCase{ + func() testCase { + return testCase{ + name: "invalid transaction address (checksum error)", + address: test.FactoryRandomString(), + client: stub.New(transaction.Transactions{}, nil), + unique: test.FactoryRandomString(), + publisherMetadata: []*publishMetadata.Instance{ + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + publishMetadata.New( + test.FactoryRandomString(), + iotaPublisherMetadata.NewSuccess( + internalTest.FactoryRandomInvalidAddressTrytesString(), + test.FactoryRandomString(), + test.FactoryRandomString(), + ), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return metadata.NewFailure("invalid address") + }, + } + }(), + func() testCase { + err := test.FactoryRandomString() + return testCase{ + name: "find transaction object failure", + address: internalTest.FactoryRandomAddressTrytesString(), + client: stub.New(nil, errors.New(err)), + unique: test.FactoryRandomString(), + publisherMetadata: []*publishMetadata.Instance{ + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + publishMetadata.New( + test.FactoryRandomString(), + iotaPublisherMetadata.NewSuccess( + internalTest.FactoryRandomAddressTrytesString(), + test.FactoryRandomString(), + test.FactoryRandomString(), + ), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return metadata.NewFailure(err) + }, + } + }(), + func() testCase { + return testCase{ + name: "extract json error", + address: internalTest.FactoryRandomAddressTrytesString(), + client: stub.New(transaction.Transactions{}, nil), + unique: test.FactoryRandomString(), + publisherMetadata: []*publishMetadata.Instance{ + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + publishMetadata.New( + test.FactoryRandomString(), + iotaPublisherMetadata.NewSuccess( + internalTest.FactoryRandomAddressTrytesString(), + test.FactoryRandomString(), + test.FactoryRandomString(), + ), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return metadata.NewFailure("invalid bundle") + }, + } + }(), + func() testCase { + return testCase{ + name: "no IOTA publisher annotation match (with IPFS publisher annotation)", + address: internalTest.FactoryRandomAddressTrytesString(), + client: stub.New(nil, nil), + unique: test.FactoryRandomString(), + publisherMetadata: []*publishMetadata.Instance{ + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return sut.failureAnnotationMatch() + }, + } + }(), + func() testCase { + return testCase{ + name: "no IPFS publisher annotation match (with IOTA publisher annotation)", + address: internalTest.FactoryRandomAddressTrytesString(), + client: stub.New(nil, nil), + unique: test.FactoryRandomString(), + publisherMetadata: []*publishMetadata.Instance{ + publishMetadata.New( + test.FactoryRandomString(), + iotaPublisherMetadata.NewSuccess( + internalTest.FactoryRandomAddressTrytesString(), + test.FactoryRandomString(), + test.FactoryRandomString(), + ), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return sut.failureAnnotationMatch() + }, + } + }(), + func() testCase { + stubbedClient := stub.New(transaction.Transactions{ + internalTest.FactoryAnnotationTransaction( + t, + test.FactoryRandomString(), + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + ), + }, nil) + return testCase{ + name: "unmarshal transaction error", + address: internalTest.FactoryRandomAddressTrytesString(), + client: stubbedClient, + unique: test.FactoryRandomString(), + publisherMetadata: []*publishMetadata.Instance{ + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + publishMetadata.New( + test.FactoryRandomString(), + iotaPublisherMetadata.NewSuccess( + internalTest.FactoryRandomAddressTrytesString(), + test.FactoryRandomString(), + test.FactoryRandomString(), + ), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return metadata.NewFailure("invalid character ':' after top-level value") + }, + } + }(), + func() testCase { + unique := test.FactoryRandomString() + txs := transaction.Transactions{ + internalTest.FactoryAnnotationsTransaction( + t, + unique, + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + ), + } + stubbedClient := stub.New(txs, nil) + + return testCase{ + name: "invalid IOTA annotation assessment", + address: internalTest.FactoryRandomAddressTrytesString(), + client: stubbedClient, + unique: unique, + publisherMetadata: []*publishMetadata.Instance{ + publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ), + publishMetadata.New( + test.FactoryRandomString(), + iotaPublisherMetadata.NewSuccess( + internalTest.FactoryRandomAddressTrytesString(), + test.FactoryRandomString(), + test.FactoryRandomString(), + ), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return metadata.NewSuccess(false, []string{unique}) + }, + } + }(), + func() testCase { + ipfsMetadata := publishMetadata.New( + test.FactoryRandomString(), + ipfsPublisherMetadata.NewSuccess(test.FactoryRandomString()), + ) + unique := test.FactoryRandomString() + stubbedClient := stub.New(transaction.Transactions{ + internalTest.FactoryAnnotationsTransaction(t, unique, ipfsMetadata), + }, nil) + return testCase{ + name: "valid IOTA annotation assessment", + address: internalTest.FactoryRandomAddressTrytesString(), + client: stubbedClient, + unique: unique, + publisherMetadata: []*publishMetadata.Instance{ + ipfsMetadata, + publishMetadata.New( + test.FactoryRandomString(), + iotaPublisherMetadata.NewSuccess( + internalTest.FactoryRandomAddressTrytesString(), + test.FactoryRandomString(), + test.FactoryRandomString(), + ), + ), + }, + expectedResult: func(sut *assessor) interface{} { + return metadata.NewSuccess(true, []string{unique}) + }, + } + }(), + } + + for i := range cases { + fmt.Println(cases[i].name) + sut := New(cases[i].client) + + publisherAnnotations := make([]*annotation.Instance, 0) + for p := range cases[i].publisherMetadata { + publisherAnnotations = append( + publisherAnnotations, + &annotation.Instance{ + Unique: cases[i].unique, + Created: test.FactoryRandomString(), + CurrentIdentityKind: test.FactoryRandomString(), + CurrentIdentity: identityProvider.New(sha256.New()).Derive(test.FactoryRandomByteSlice()), + PreviousIdentityKind: test.FactoryRandomString(), + PreviousIdentity: nil, + MetadataKind: publishMetadata.Kind, + Metadata: cases[i].publisherMetadata[p], + }, + ) + } + result := sut.Assess(publisherAnnotations) + + assert.Equal(t, cases[i].expectedResult(sut), result) + } +} + +// TestAssessor_Failure tests assessor.Failure +func TestAssessor_Failure(t *testing.T) { + sut := New(stub.New(transaction.Transactions{}, nil)) + + err := test.FactoryRandomString() + result := sut.Failure(err) + + assert.Equal(t, metadata.NewFailure(err), result) +} + +func TestAssessor_Kind(t *testing.T) { + sut := New(stub.New(transaction.Transactions{}, nil)) + + result := sut.Kind() + + assert.Equal(t, metadata.Kind, result) +} diff --git a/pkg/annotator/assess/assessor/iota/client/contract.go b/pkg/annotator/assess/assessor/iota/client/contract.go new file mode 100644 index 0000000..d0c5289 --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/client/contract.go @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 client + +import ( + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/transaction" +) + +// Contract is an abstraction for an IOTA client. +type Contract interface { + // FindTransactionObjects is called to return Transactions for a given query. + FindTransactionObjects(query api.FindTransactionsQuery) (transaction.Transactions, error) +} diff --git a/pkg/annotator/assess/assessor/iota/client/stub/stub.go b/pkg/annotator/assess/assessor/iota/client/stub/stub.go new file mode 100644 index 0000000..7f655fd --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/client/stub/stub.go @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 stub + +import ( + "github.com/iotaledger/iota.go/api" + "github.com/iotaledger/iota.go/transaction" +) + +// client is a receiver that encapsulates dependencies. +type client struct { + findTxObjectsResult transaction.Transactions + findTxObjectsErr error +} + +// New is a factory function that returns an initialized instance. +func New(findTxObjectsResult transaction.Transactions, findTxObjectErr error) *client { + return &client{ + findTxObjectsResult: findTxObjectsResult, + findTxObjectsErr: findTxObjectErr, + } +} + +// FindTransactionObjects is called to retrieve transactions for the specified query. +func (c *client) FindTransactionObjects(query api.FindTransactionsQuery) (transaction.Transactions, error) { + if c.findTxObjectsErr != nil { + return nil, c.findTxObjectsErr + } + + return c.findTxObjectsResult, nil +} diff --git a/pkg/annotator/assess/assessor/iota/metadata/failure.go b/pkg/annotator/assess/assessor/iota/metadata/failure.go new file mode 100644 index 0000000..0806bdb --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/metadata/failure.go @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 metadata + +import "github.com/project-alvarium/go-sdk/pkg/annotator" + +// Failure defines the structure that encapsulates this assessor's result. +type Failure struct { + Result string `json:"result"` + ErrorMessage string `json:"errorMessage"` +} + +// NewFailure is a factory function that returns an initialized Failure. +func NewFailure(errorMessage string) *Failure { + return &Failure{ + Result: annotator.FailureKind, + ErrorMessage: errorMessage, + } +} + +// Kind returns the type of concrete implementation. +func (f *Failure) Kind() string { + return Kind +} diff --git a/pkg/annotator/assess/assessor/iota/metadata/failure_test.go b/pkg/annotator/assess/assessor/iota/metadata/failure_test.go new file mode 100644 index 0000000..2be3fb3 --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/metadata/failure_test.go @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 metadata + +import ( + "testing" + + "github.com/project-alvarium/go-sdk/pkg/test" + + "github.com/magiconair/properties/assert" +) + +// TestFailure_Kind tests failure.Kind. +func TestFailure_Kind(t *testing.T) { + sut := NewFailure(test.FactoryRandomString()) + + assert.Equal(t, Kind, sut.Kind()) +} diff --git a/pkg/annotator/assess/assessor/iota/metadata/kind.go b/pkg/annotator/assess/assessor/iota/metadata/kind.go new file mode 100644 index 0000000..97e8df3 --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/metadata/kind.go @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 metadata + +import iotaPublisherMetadata "github.com/project-alvarium/go-sdk/pkg/annotator/publish/publisher/iota/metadata" + +const Kind = iotaPublisherMetadata.Kind diff --git a/pkg/annotator/assess/assessor/iota/metadata/success.go b/pkg/annotator/assess/assessor/iota/metadata/success.go new file mode 100644 index 0000000..ea88b58 --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/metadata/success.go @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 metadata + +import "github.com/project-alvarium/go-sdk/pkg/annotator" + +// Success defines the structure that encapsulates this verifier's assessment. +type Success struct { + Result string `json:"result"` + ValidSignature bool `json:"validSignature"` + Unique []string `json:"unique"` +} + +// NewSuccess is a factory function that returns an initialized Success. +func NewSuccess(validSignature bool, unique []string) *Success { + return &Success{ + Result: annotator.SuccessKind, + ValidSignature: validSignature, + Unique: unique, + } +} + +// Kind returns the type of concrete implementation. +func (s *Success) Kind() string { + return Kind +} diff --git a/pkg/annotator/assess/assessor/iota/metadata/success_test.go b/pkg/annotator/assess/assessor/iota/metadata/success_test.go new file mode 100644 index 0000000..57a6904 --- /dev/null +++ b/pkg/annotator/assess/assessor/iota/metadata/success_test.go @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright 2020 Dell Inc. + * + * 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 metadata + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestSuccess_Kind tests success.Kind. +func TestSuccess_Kind(t *testing.T) { + sut := NewSuccess(true, []string{}) + + assert.Equal(t, Kind, sut.Kind()) +}