github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/automation/spec/workflow_store.go (about)

     1  package spec
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/qri-io/qri/automation/hook"
    13  	"github.com/qri-io/qri/automation/trigger"
    14  	"github.com/qri-io/qri/automation/workflow"
    15  	"github.com/qri-io/qri/base/params"
    16  	"github.com/qri-io/qri/profile"
    17  )
    18  
    19  // AssertWorkflowStore confirms the expected behavior of a workflow.Store Interface
    20  // implementation
    21  func AssertWorkflowStore(t *testing.T, store workflow.Store) {
    22  	ctx := context.Background()
    23  	seedStr := "workflow assert store seed string used for testing in the workflow package"
    24  	workflow.SetIDRand(strings.NewReader(seedStr))
    25  	now := time.Now()
    26  
    27  	aliceInitID := "alice_dataset_id"
    28  	aliceProID := profile.ID("alice_pro_id")
    29  	aliceTrigger := map[string]interface{}{"type": trigger.RuntimeType}
    30  	aliceHook := map[string]interface{}{"type": hook.RuntimeType}
    31  	alice := &workflow.Workflow{
    32  		InitID:   aliceInitID,
    33  		OwnerID:  aliceProID,
    34  		Created:  &now,
    35  		Triggers: []map[string]interface{}{aliceTrigger},
    36  		Hooks:    []map[string]interface{}{aliceHook},
    37  	}
    38  	got, err := store.Put(ctx, alice)
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	if got.ID == "" {
    43  		t.Errorf("store.Put error: a workflow with no ID is considered a new workflow. The workflow.Store should create an ID and return the workflow with the generated ID")
    44  	}
    45  	alice.ID = got.ID
    46  	aliceID := alice.ID
    47  	if diff := cmp.Diff(alice, got); diff != "" {
    48  		t.Errorf("workflow mismatch (-want +got):\n%s", diff)
    49  	}
    50  
    51  	got, err = store.Get(ctx, aliceID)
    52  	if err != nil {
    53  		t.Fatal(err)
    54  	}
    55  	if diff := cmp.Diff(alice, got); diff != "" {
    56  		t.Errorf("workflow mismatch (-want +got):\n%s", diff)
    57  	}
    58  
    59  	got, err = store.GetByInitID(ctx, alice.InitID)
    60  	if err != nil {
    61  		t.Fatal(err)
    62  	}
    63  	if diff := cmp.Diff(alice, got); diff != "" {
    64  		t.Errorf("workflow mismatch (-want +got):\n%s", diff)
    65  	}
    66  
    67  	// store.Put error checking
    68  	if _, err := store.Put(ctx, &workflow.Workflow{InitID: aliceInitID}); !errors.Is(err, workflow.ErrWorkflowForDatasetExists) {
    69  		t.Errorf("Put method must emit `workflow.ErrWorkflowForDatasetExists` error if a workflow for the given InitID already exists")
    70  	}
    71  
    72  	brittInitID := "britt_dataset_id"
    73  	brittProID := profile.ID("britt_pro_id")
    74  	brittTrigger := map[string]interface{}{"type": trigger.RuntimeType}
    75  	brittHook := map[string]interface{}{"type": hook.RuntimeType}
    76  	britt := &workflow.Workflow{
    77  		InitID:   brittInitID,
    78  		OwnerID:  brittProID,
    79  		Created:  &now,
    80  		Triggers: []map[string]interface{}{brittTrigger},
    81  		Hooks:    []map[string]interface{}{brittHook},
    82  	}
    83  	got, err = store.Put(ctx, britt)
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	britt.ID = got.ID
    89  	if diff := cmp.Diff(britt, got); diff != "" {
    90  		t.Errorf("workflow mismatch (-want +got):\n%s", diff)
    91  	}
    92  
    93  	wfs, err := store.List(ctx, "", params.ListAll)
    94  	if err != nil {
    95  		t.Fatal(err)
    96  	}
    97  	if len(wfs) != 2 {
    98  		t.Fatalf("store.List count mismatch, expected 2 workflows, got %d", len(wfs))
    99  	}
   100  
   101  	deployed, err := store.ListDeployed(ctx, "", params.ListAll)
   102  	if err != nil {
   103  		t.Fatal(err)
   104  	}
   105  
   106  	if len(deployed) != 0 {
   107  		t.Fatalf("store.ListDeployed count mismatch, expected 0 workflows, got %d", len(deployed))
   108  	}
   109  
   110  	aliceUpdated := &workflow.Workflow{
   111  		ID:       aliceID,
   112  		InitID:   alice.InitID,
   113  		OwnerID:  alice.OwnerID,
   114  		Active:   true,
   115  		Created:  &now,
   116  		Triggers: []map[string]interface{}{aliceTrigger, brittTrigger},
   117  		Hooks:    []map[string]interface{}{aliceHook, brittHook},
   118  	}
   119  	_, err = store.Put(ctx, aliceUpdated)
   120  	if err != nil {
   121  		t.Fatal(err)
   122  	}
   123  
   124  	got, err = store.Get(ctx, aliceID)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	if diff := cmp.Diff(aliceUpdated, got); diff != "" {
   129  		t.Errorf("workflow mismatch (-want +got):\n%s", diff)
   130  	}
   131  
   132  	deployed, err = store.ListDeployed(ctx, "", params.ListAll)
   133  	if err != nil {
   134  		t.Fatal(err)
   135  	}
   136  
   137  	if len(deployed) != 1 {
   138  		t.Fatalf("store.ListDeployed count mismatch, expected 1 workflow, got %d", len(deployed))
   139  	}
   140  	if diff := cmp.Diff(aliceUpdated, deployed[0]); diff != "" {
   141  		t.Errorf("workflow mismatch (-want +got):\n%s", diff)
   142  	}
   143  
   144  	err = store.Remove(ctx, aliceID)
   145  	if err != nil {
   146  		t.Fatal(err)
   147  	}
   148  
   149  	_, err = store.Get(ctx, aliceID)
   150  	if !errors.Is(err, workflow.ErrNotFound) {
   151  		t.Errorf("store.Get error mistmatch, expected %q, got %q", workflow.ErrNotFound, err)
   152  	}
   153  }
   154  
   155  // AssertWorkflowLister confirms the expected behavior of a workflow.Lister Interface
   156  // implementation
   157  func AssertWorkflowLister(t *testing.T, store workflow.Store) {
   158  	// set up
   159  	workflow.SetIDRand(strings.NewReader(strings.Repeat("Lorem ipsum dolor sit amet", 20)))
   160  	ctx := context.Background()
   161  	expectedAllWorkflows := [10]*workflow.Workflow{}
   162  	expectedDeployedWorkflows := [5]*workflow.Workflow{}
   163  
   164  	proID := profile.ID("profile_id")
   165  	for i := 0; i < 10; i++ {
   166  		now := time.Now()
   167  		wf, err := store.Put(ctx, &workflow.Workflow{
   168  			InitID:  fmt.Sprintf("dataset_%d", i),
   169  			OwnerID: proID,
   170  			Created: &now,
   171  		})
   172  		if err != nil {
   173  			t.Fatal(err)
   174  		}
   175  		if i%2 == 0 {
   176  			wf.Active = true
   177  			expectedDeployedWorkflows[4-(i/2)] = wf
   178  		}
   179  		expectedAllWorkflows[9-i] = wf
   180  	}
   181  
   182  	// error cases
   183  	errCases := []errTestCase{
   184  		{"negative limit", "test_owner", -10, 0, "limit of -10 is out of bounds"},
   185  		{"negative offset", "test_owner", 0, -1, "offset of -1 is out of bounds"},
   186  	}
   187  
   188  	runListErrTestCases(ctx, t, "List", store.List, errCases)
   189  	runListErrTestCases(ctx, t, "ListDeployed", store.ListDeployed, errCases)
   190  
   191  	// empty list cases
   192  	emptyCases := []emptyTestCase{
   193  		{"offset out of bounds", 10, 100},
   194  		{"zero limit", 0, 0},
   195  	}
   196  
   197  	runListEmptyTestCases(ctx, t, "List", store.List, emptyCases)
   198  	runListEmptyTestCases(ctx, t, "ListDeployed", store.ListDeployed, emptyCases)
   199  
   200  	// working cases
   201  	cases := []expectedTestCase{
   202  		{"get all", "test_owner", -1, 0, expectedAllWorkflows[:]},
   203  		{"get first 4", "test_owner", 4, 0, expectedAllWorkflows[0:4]},
   204  		{"get next 4", "test_owner", 4, 4, expectedAllWorkflows[4:8]},
   205  		{"get last 2", "test_owner", 4, 8, expectedAllWorkflows[8:]},
   206  	}
   207  
   208  	runListExpectedTestCases(ctx, t, "List", store.List, cases)
   209  
   210  	cases = []expectedTestCase{
   211  		{"get all", "test_owner", -1, 0, expectedDeployedWorkflows[:]},
   212  		{"get first 2", "test_owner", 2, 0, expectedDeployedWorkflows[0:2]},
   213  		{"get next 2", "test_owner", 2, 2, expectedDeployedWorkflows[2:4]},
   214  		{"get last 1", "test_owner", 2, 4, expectedDeployedWorkflows[4:]},
   215  	}
   216  	runListExpectedTestCases(ctx, t, "ListDeployed", store.ListDeployed, cases)
   217  }
   218  
   219  type expectedTestCase struct {
   220  	description string
   221  	ownerID     profile.ID
   222  	limit       int
   223  	offset      int
   224  	expected    []*workflow.Workflow
   225  }
   226  
   227  func runListExpectedTestCases(ctx context.Context, t *testing.T, fnName string, fn listFunc, cases []expectedTestCase) {
   228  	for _, c := range cases {
   229  		got, err := fn(ctx, c.ownerID, params.List{}.WithOffsetLimit(c.offset, c.limit))
   230  		if err != nil {
   231  			t.Errorf("%s case %s: unexpected error %w", fnName, c.description, err)
   232  			continue
   233  		}
   234  		if diff := cmp.Diff(c.expected, got); diff != "" {
   235  			t.Errorf("%s case %s: workflow list mismatch (-want +got):\n%s", fnName, c.description, diff)
   236  		}
   237  	}
   238  }
   239  
   240  type listFunc func(ctx context.Context, pid profile.ID, lp params.List) ([]*workflow.Workflow, error)
   241  
   242  type errTestCase struct {
   243  	description string
   244  	ownerID     profile.ID
   245  	limit       int
   246  	offset      int
   247  	errMsg      string
   248  }
   249  
   250  func runListErrTestCases(ctx context.Context, t *testing.T, fnName string, fn listFunc, cases []errTestCase) {
   251  	for _, c := range cases {
   252  		_, err := fn(ctx, c.ownerID, params.List{}.WithOffsetLimit(c.offset, c.limit))
   253  		if err == nil {
   254  			t.Errorf("%s case %s: error mismatch, expected %q, got no error", fnName, c.description, c.errMsg)
   255  			continue
   256  		}
   257  		if err.Error() != c.errMsg {
   258  			t.Errorf("%s case %s: error mismatch, expected %q, got %q", fnName, c.description, c.errMsg, err)
   259  		}
   260  	}
   261  }
   262  
   263  type emptyTestCase struct {
   264  	description string
   265  	limit       int
   266  	offset      int
   267  }
   268  
   269  func runListEmptyTestCases(ctx context.Context, t *testing.T, fnName string, fn listFunc, cases []emptyTestCase) {
   270  	expected := []*workflow.Workflow{}
   271  	for _, c := range cases {
   272  		got, err := fn(ctx, "", params.List{}.WithOffsetLimit(c.offset, c.limit))
   273  		if err != nil {
   274  			t.Errorf("%s case %s: unexpected error %q", fnName, c.description, err)
   275  			continue
   276  		}
   277  		if diff := cmp.Diff(expected, got); diff != "" {
   278  			t.Errorf("%s case %s: workflow list mismatch (-want +got):\n%s", fnName, c.description, diff)
   279  		}
   280  	}
   281  }