Skip to content

Commit

Permalink
Included Unit Test for Subscribe (#1056)
Browse files Browse the repository at this point in the history
* Inlcuded a unit test for `Subscribe`
* Not included for error conditions as it will require mocking

Signed-off-by: nathannaveen <42319948+nathannaveen@users.noreply.github.com>
  • Loading branch information
nathannaveen committed Jul 17, 2023
1 parent 3cc7cde commit 8b799ed
Show file tree
Hide file tree
Showing 3 changed files with 218 additions and 4 deletions.
4 changes: 2 additions & 2 deletions pkg/ingestor/parser/common/types.go
Expand Up @@ -34,13 +34,13 @@ type DocumentParser interface {

// GetIdentifiers returns a set of identifiers that the parser has found to help provide context
// for collectors to gather more information around found software identifiers.
// This is an optional function to implement and it should return an error if not implemented.
// This is an optional function to implement, and it should return an error if not implemented.
//
// Ref: https://github.com/guacsec/guac/issues/244
GetIdentifiers(ctx context.Context) (*IdentifierStrings, error)
}

// Identifiers represent a set of strings that can be used to a set of
// IdentifierStrings represent a set of strings that can be used to a set of
// identifiers that the parser has found to help provide context for collectors
// to gather more information around found software identifiers.
//
Expand Down
5 changes: 3 additions & 2 deletions pkg/ingestor/parser/parser.go
Expand Up @@ -74,6 +74,7 @@ func RegisterDocumentParser(p func() common.DocumentParser, d processor.Document

// Subscribe is used by NATS JetStream to stream the documents received from the processor
// and parse them via ParseDocumentTree
// The context contains the jetstream.
func Subscribe(ctx context.Context, transportFunc func([]assembler.IngestPredicates, []*common.IdentifierStrings) error) error {
logger := logging.FromContext(ctx)

Expand All @@ -90,12 +91,12 @@ func Subscribe(ctx context.Context, transportFunc func([]assembler.IngestPredica
// should still continue if there are errors since problem is with individual documents
parserFunc := func(d []byte) error {
docNode := processor.DocumentNode{}
err := json.Unmarshal(d, &docNode)
err = json.Unmarshal(d, &docNode)
if err != nil {
logger.Error("[ingestor: %s] failed unmarshal the document tree bytes: %v", uuidString, err)
return nil
}
assemblerInputs, idStrings, err := ParseDocumentTree(ctx, processor.DocumentTree(&docNode))
assemblerInputs, idStrings, err := ParseDocumentTree(ctx, &docNode)
if err != nil {
logger.Error("[ingestor: %s] failed parse document: %v", uuidString, err)
return nil
Expand Down
213 changes: 213 additions & 0 deletions pkg/ingestor/parser/parser_test.go
Expand Up @@ -17,8 +17,19 @@ package parser

import (
"context"
"encoding/json"
"errors"
"reflect"
"strings"
"testing"
"time"

"github.com/guacsec/guac/internal/testing/mockverifier"
nats_test "github.com/guacsec/guac/internal/testing/nats"
"github.com/guacsec/guac/pkg/assembler"
"github.com/guacsec/guac/pkg/emitter"
"github.com/guacsec/guac/pkg/ingestor/verifier"
"github.com/guacsec/guac/pkg/logging"

"github.com/guacsec/guac/internal/testing/mocks"

Expand Down Expand Up @@ -209,3 +220,205 @@ func Test_docTreeBuilder_parse(t *testing.T) {
})
}
}

func TestParseDocumentTree(t *testing.T) {
tests := []struct {
name string
docTree processor.DocumentTree
registerDocType processor.DocumentType
// The registerDocType is used to register the document parser, it is different from the roots own
// processor.DocumentType so that we can test the error case
getIdentifiersShouldErr bool
want []assembler.IngestPredicates
want1 []*common.IdentifierStrings
wantErr bool
}{
{
name: "default",
docTree: &processor.DocumentNode{
Document: &processor.Document{
Type: "test",
},
},
registerDocType: "test",
want: []assembler.IngestPredicates{{}},
want1: []*common.IdentifierStrings{{}},
},
{
name: "parse error",
docTree: &processor.DocumentNode{
Document: &processor.Document{
Type: "invalid",
},
},
registerDocType: "test",
wantErr: true,
},
{
name: "get identities error",
docTree: &processor.DocumentNode{
Document: &processor.Document{
Type: "test",
},
},
registerDocType: "test",
getIdentifiersShouldErr: true,
want: []assembler.IngestPredicates{{}},
want1: []*common.IdentifierStrings{},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockDocumentParser := mocks.NewMockDocumentParser(ctrl)
ctx := context.Background()

parser := common.DocumentParser(mockDocumentParser)

f := func() common.DocumentParser {
return parser
}

mockDocumentParser.EXPECT().Parse(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, doc *processor.Document) error {
return nil
}).AnyTimes()
mockDocumentParser.EXPECT().GetIdentities(ctx).Return([]common.TrustInformation{}).AnyTimes()
mockDocumentParser.EXPECT().GetPredicates(gomock.Any()).Return(&assembler.IngestPredicates{}).AnyTimes()
mockDocumentParser.EXPECT().GetIdentifiers(gomock.Any()).DoAndReturn(func(ctx context.Context) (*common.IdentifierStrings, error) {
if test.getIdentifiersShouldErr {
return nil, errors.New("get identities error")
}
return &common.IdentifierStrings{}, nil
}).AnyTimes()

_ = RegisterDocumentParser(f, test.registerDocType) // Ignoring error because it is mutating a global variable

got, got1, err := ParseDocumentTree(ctx, test.docTree)

if (err != nil) != test.wantErr {
t.Errorf("ParseDocumentTree() error = %v, wantErr %v", err, test.wantErr)
return
}
if !reflect.DeepEqual(got, test.want) {
t.Errorf("ParseDocumentTree() got = %v, want %v", got, test.want)
}
if !reflect.DeepEqual(got1, test.want1) {
t.Errorf("ParseDocumentTree() got1 = %v, want %v", got1, test.want1)
}
})
}
}

func TestSubscribe(t *testing.T) {
ctx := logging.WithLogger(context.Background())
err := verifier.RegisterVerifier(mockverifier.NewMockSigstoreVerifier(), "sigstore")
if err != nil {
if !strings.Contains(err.Error(), "the verification provider is being overwritten") {
t.Errorf("unexpected error: %v", err)
}
}

natsTest := nats_test.NewNatsTestServer()
url, err := natsTest.EnableJetStreamForTest()
if err != nil {
t.Fatal(err)
}
defer natsTest.Shutdown()

tests := []struct {
name string
registerDocType processor.DocumentType
// The registerDocType is used to register the document parser, it is different from the roots own
// processor.DocumentType so that we can test the error case
tree processor.DocumentTree
want []assembler.AssemblerInput
parseWant []assembler.IngestPredicates
parseWant1 []*common.IdentifierStrings
wantErr bool
}{
{
name: "default",
registerDocType: "test",
tree: &processor.DocumentNode{
Document: &processor.Document{
Type: "test",
},
},
parseWant: []assembler.IngestPredicates{{}},
parseWant1: []*common.IdentifierStrings{{}},
wantErr: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
jetStream := emitter.NewJetStream(url, "", "")
ctx, err = jetStream.JetStreamInit(ctx)
if err != nil {
t.Fatalf("unexpected error initializing jetstream: %v", err)
}
err = jetStream.RecreateStream(ctx)
if err != nil {
t.Fatalf("unexpected error recreating jetstream: %v", err)
}
defer jetStream.Close()
err := testPublish(ctx, test.tree)
if err != nil {
t.Fatalf("unexpected error on emit: %v", err)
}
var cancel context.CancelFunc

ctx, cancel = context.WithTimeout(ctx, time.Second)
defer cancel()

ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockDocumentParser := mocks.NewMockDocumentParser(ctrl)

parser := common.DocumentParser(mockDocumentParser)

f := func() common.DocumentParser {
return parser
}

mockDocumentParser.EXPECT().Parse(gomock.Any(), gomock.Any()).DoAndReturn(func(ctx context.Context, doc *processor.Document) error {
return nil
}).AnyTimes()
mockDocumentParser.EXPECT().GetIdentities(ctx).Return([]common.TrustInformation{}).AnyTimes()
mockDocumentParser.EXPECT().GetPredicates(gomock.Any()).Return(&assembler.IngestPredicates{}).AnyTimes()
mockDocumentParser.EXPECT().GetIdentifiers(gomock.Any()).DoAndReturn(func(ctx context.Context) (*common.IdentifierStrings, error) {
return &common.IdentifierStrings{}, nil
}).AnyTimes()

_ = RegisterDocumentParser(f, test.registerDocType) // Ignoring error because it is mutating a global variable

transportFunc := func(x []assembler.IngestPredicates, y []*common.IdentifierStrings) error {
if !reflect.DeepEqual(x, test.parseWant) {
t.Errorf("Subscribe() got = %v, want %v", x, test.parseWant)
}
if !reflect.DeepEqual(y, test.parseWant1) {
t.Errorf("Subscribe() got1 = %v, want %v", y, test.parseWant1)
}
return nil
}

if err := Subscribe(ctx, transportFunc); (err != nil) != test.wantErr {
t.Errorf("Subscribe() error = %v, wantErr %v", err, test.wantErr)
}

delete(documentParser, test.registerDocType)
})
}
}

func testPublish(ctx context.Context, documentTree processor.DocumentTree) error {
docTreeJSON, err := json.Marshal(documentTree)
if err != nil {
return err
}
err = emitter.Publish(ctx, emitter.SubjectNameDocProcessed, docTreeJSON)
if err != nil {
return err
}
return nil
}

0 comments on commit 8b799ed

Please sign in to comment.