Skip to content

Commit

Permalink
feat: update ack store (#1799)
Browse files Browse the repository at this point in the history
Signed-off-by: Misha Sizov <mykhailo.sizov@securekey.com>
  • Loading branch information
mishasizov-SK authored Nov 22, 2024
1 parent c7f6933 commit c0362a2
Show file tree
Hide file tree
Showing 7 changed files with 418 additions and 252 deletions.
420 changes: 210 additions & 210 deletions api/spec/openapi.gen.go

Large diffs are not rendered by default.

32 changes: 27 additions & 5 deletions pkg/service/oidc4ci/oidc4ci_acknowledgement.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ func NewAckService(
}
}

// CreateAck creates an acknowledgement.
func (s *AckService) CreateAck(
// UpsertAck creates an acknowledgement if it does not exist in store, and updates in case it exists.
// Designed to be able to count amount of possible /ack request for given transaction.
func (s *AckService) UpsertAck(
ctx context.Context,
ack *Ack,
) (string, error) {
Expand All @@ -51,9 +52,30 @@ func (s *AckService) CreateAck(
return "", err
}

id, err := s.cfg.AckStore.Create(ctx, profile.DataConfig.OIDC4CIAckDataTTL, ack)
if err != nil {
return "", err
// id (AKA notification_id) should be the same as txID
// in order to be able to sent spi.IssuerOIDCInteractionAckExpired event with proper txID.
// But, txID value might also be extracted from token.
id := string(ack.TxID)

var existingAck *Ack
existingAck, err = s.cfg.AckStore.Get(ctx, id)
if err != nil && !errors.Is(err, ErrDataNotFound) {
return "", fmt.Errorf("get existing ack: %w", err)
}

// If ack is ready exists.
if existingAck != nil {
ack.CredentialsIssued += existingAck.CredentialsIssued

if err = s.cfg.AckStore.Update(ctx, id, ack); err != nil { // not critical
return "", fmt.Errorf("update ack with id[%s]: %s", id, err.Error())
}

return id, nil
}

if err = s.cfg.AckStore.Create(ctx, id, profile.DataConfig.OIDC4CIAckDataTTL, ack); err != nil {
return "", fmt.Errorf("create ack: %w", err)
}

return id, nil
Expand Down
168 changes: 157 additions & 11 deletions pkg/service/oidc4ci/oidc4ci_acknowledgement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,28 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"testing"

"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"

"github.com/trustbloc/vcs/pkg/event/spi"
"github.com/trustbloc/vcs/pkg/profile"
"github.com/trustbloc/vcs/pkg/service/issuecredential"
"github.com/trustbloc/vcs/pkg/service/oidc4ci"
)

func TestCreateAck(t *testing.T) {
func TestUpsertAck(t *testing.T) {
t.Run("missing store", func(t *testing.T) {
srv := oidc4ci.NewAckService(&oidc4ci.AckServiceConfig{})
id, err := srv.CreateAck(context.TODO(), &oidc4ci.Ack{})
id, err := srv.UpsertAck(context.TODO(), &oidc4ci.Ack{})
assert.NoError(t, err)
assert.Empty(t, id)
})

t.Run("success", func(t *testing.T) {
t.Run("success: new ack", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(&profile.Issuer{
Expand All @@ -41,16 +44,72 @@ func TestCreateAck(t *testing.T) {
AckStore: store,
})

txID := uuid.NewString()

item := &oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
}

store.EXPECT().Create(gomock.Any(), int32(10), item).Return("id", nil)
id, err := srv.CreateAck(context.TODO(), item)
store.EXPECT().
Get(gomock.Any(), txID).
Return(nil, oidc4ci.ErrDataNotFound)

store.EXPECT().
Create(gomock.Any(), txID, int32(10), item).
Return(nil)

id, err := srv.UpsertAck(context.TODO(), item)

assert.NoError(t, err)
assert.Equal(t, txID, id)
})

t.Run("success: existing ack", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(&profile.Issuer{
DataConfig: profile.IssuerDataConfig{OIDC4CIAckDataTTL: 10},
}, nil)

store := NewMockAckStore(gomock.NewController(t))
srv := oidc4ci.NewAckService(&oidc4ci.AckServiceConfig{
ProfileSvc: profileSvc,
AckStore: store,
})

txID := uuid.NewString()

store.EXPECT().
Get(gomock.Any(), txID).
Return(&oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
CredentialsIssued: 2,
}, nil)

store.EXPECT().
Update(gomock.Any(), txID, &oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
CredentialsIssued: 3, // 2+1
}).
Return(nil)

item := &oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
CredentialsIssued: 1,
}

id, err := srv.UpsertAck(context.TODO(), item)

assert.NoError(t, err)
assert.Equal(t, "id", id)
assert.Equal(t, txID, id)
})

t.Run("profile srv err", func(t *testing.T) {
Expand All @@ -69,13 +128,89 @@ func TestCreateAck(t *testing.T) {
ProfileVersion: "v1.0",
}

id, err := srv.CreateAck(context.TODO(), item)
id, err := srv.UpsertAck(context.TODO(), item)

assert.Empty(t, id)
assert.ErrorContains(t, err, "some error")
})

t.Run("store err", func(t *testing.T) {
t.Run("success: get store error", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(&profile.Issuer{
DataConfig: profile.IssuerDataConfig{OIDC4CIAckDataTTL: 10},
}, nil)

store := NewMockAckStore(gomock.NewController(t))
srv := oidc4ci.NewAckService(&oidc4ci.AckServiceConfig{
ProfileSvc: profileSvc,
AckStore: store,
})

txID := uuid.NewString()

item := &oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
}

store.EXPECT().
Get(gomock.Any(), txID).
Return(nil, errors.New("some error"))

id, err := srv.UpsertAck(context.TODO(), item)
assert.ErrorContains(t, err, "get existing ack: some error")
assert.Empty(t, id)
})

t.Run("error: existing ack: udpate error", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(&profile.Issuer{
DataConfig: profile.IssuerDataConfig{OIDC4CIAckDataTTL: 10},
}, nil)

store := NewMockAckStore(gomock.NewController(t))
srv := oidc4ci.NewAckService(&oidc4ci.AckServiceConfig{
ProfileSvc: profileSvc,
AckStore: store,
})

txID := uuid.NewString()

store.EXPECT().
Get(gomock.Any(), txID).
Return(&oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
CredentialsIssued: 2,
}, nil)

store.EXPECT().
Update(gomock.Any(), txID, &oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
CredentialsIssued: 3, // 2+1
}).
Return(errors.New("some error"))

item := &oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
CredentialsIssued: 1,
}

id, err := srv.UpsertAck(context.TODO(), item)

assert.ErrorContains(t, err, fmt.Sprintf("update ack with id[%s]: some error", txID))
assert.Empty(t, id)
})

t.Run("error: new ack: create error", func(t *testing.T) {
profileSvc := NewMockProfileService(gomock.NewController(t))
profileSvc.EXPECT().GetProfile("some_issuer", "v1.0").
Return(&profile.Issuer{
Expand All @@ -88,15 +223,26 @@ func TestCreateAck(t *testing.T) {
AckStore: store,
})

txID := uuid.NewString()

item := &oidc4ci.Ack{
TxID: issuecredential.TxID(txID),
ProfileID: "some_issuer",
ProfileVersion: "v1.0",
}
store.EXPECT().Create(gomock.Any(), int32(10), item).Return("", errors.New("some err"))
id, err := srv.CreateAck(context.TODO(), item)

store.EXPECT().
Get(gomock.Any(), txID).
Return(nil, oidc4ci.ErrDataNotFound)

store.EXPECT().
Create(gomock.Any(), txID, int32(10), item).
Return(errors.New("some error"))

id, err := srv.UpsertAck(context.TODO(), item)

assert.ErrorContains(t, err, "create ack: some error")
assert.Empty(t, id)
assert.ErrorContains(t, err, "some err")
})
}

Expand Down
6 changes: 3 additions & 3 deletions pkg/service/oidc4ci/oidc4ci_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,14 +133,14 @@ type trustRegistry interface {
}

type ackStore interface {
Create(ctx context.Context, profileAckDataTTL int32, data *Ack) (string, error)
Create(ctx context.Context, id string, profileAckDataTTL int32, data *Ack) error
Get(ctx context.Context, id string) (*Ack, error)
Delete(ctx context.Context, id string) error
Update(ctx context.Context, id string, ack *Ack) error
}

type ackService interface {
CreateAck(ctx context.Context, ack *Ack) (string, error)
UpsertAck(ctx context.Context, ack *Ack) (string, error)
}

// DocumentLoader knows how to load remote documents.
Expand Down Expand Up @@ -802,7 +802,7 @@ func (s *Service) PrepareCredential( //nolint:funlen
}

if credentialsIssued := len(prepareCredentialResult.Credentials); credentialsIssued > 0 {
prepareCredentialResult.NotificationID, err = s.ackService.CreateAck(ctx, &Ack{
prepareCredentialResult.NotificationID, err = s.ackService.UpsertAck(ctx, &Ack{
TxID: tx.ID,
HashedToken: req.HashedToken,
ProfileID: tx.ProfileID,
Expand Down
18 changes: 9 additions & 9 deletions pkg/service/oidc4ci/oidc4ci_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4125,7 +4125,7 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, ack *oidc4ci.Ack) (string, error) {
expected := &oidc4ci.Ack{
HashedToken: "hashedToken",
Expand Down Expand Up @@ -4247,7 +4247,7 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, ack *oidc4ci.Ack) (string, error) {
return "ackID", nil
})
Expand Down Expand Up @@ -4341,7 +4341,7 @@ func TestService_PrepareCredential(t *testing.T) {
}, nil)

claimData := `{"surname":"Smith","givenName":"Pat","jobTitle":"Worker"}`
m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, ack *oidc4ci.Ack) (string, error) {
return "ackID", nil
})
Expand Down Expand Up @@ -4416,7 +4416,7 @@ func TestService_PrepareCredential(t *testing.T) {
}, nil)

claimData := `{"surname":"Smith","givenName":"Pat","jobTitle":"Worker"}`
m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, ack *oidc4ci.Ack) (string, error) {
return "ackID", nil
})
Expand Down Expand Up @@ -4505,7 +4505,7 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, ack *oidc4ci.Ack) (string, error) {
assert.Equal(t, "orgID1", ack.OrgID)
assert.Equal(t, "https://example.com/webhook", ack.WebHookURL)
Expand Down Expand Up @@ -4625,7 +4625,7 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, ack *oidc4ci.Ack) (string, error) {
assert.Equal(t, "asdasd", ack.OrgID)
assert.Equal(t, "aaaaa", ack.WebHookURL)
Expand Down Expand Up @@ -4800,7 +4800,7 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
Return("", errors.New("can not create ack"))

m.eventService.EXPECT().Publish(gomock.Any(), spi.IssuerEventTopic, gomock.Any()).
Expand Down Expand Up @@ -4923,7 +4923,7 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
Return("123", nil)

m.transactionStore.EXPECT().Update(gomock.Any(), gomock.Any()).
Expand Down Expand Up @@ -4991,7 +4991,7 @@ func TestService_PrepareCredential(t *testing.T) {
},
}, nil)

m.ackService.EXPECT().CreateAck(gomock.Any(), gomock.Any()).
m.ackService.EXPECT().UpsertAck(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, ack *oidc4ci.Ack) (string, error) {
return "ackID", nil
})
Expand Down
Loading

0 comments on commit c0362a2

Please sign in to comment.