github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/lib/render_test.go (about)

     1  package lib
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/google/go-cmp/cmp"
    10  	"github.com/qri-io/dataset"
    11  	"github.com/qri-io/qri/base"
    12  	"github.com/qri-io/qri/base/dsfs"
    13  	testcfg "github.com/qri-io/qri/config/test"
    14  	"github.com/qri-io/qri/dsref"
    15  	"github.com/qri-io/qri/event"
    16  	"github.com/qri-io/qri/p2p"
    17  	"github.com/qri-io/qri/repo"
    18  	testrepo "github.com/qri-io/qri/repo/test"
    19  )
    20  
    21  // renderTestRunner holds state to make it easier to run tests
    22  type renderTestRunner struct {
    23  	Node        *p2p.QriNode
    24  	Repo        repo.Repo
    25  	Instance    *Instance
    26  	Context     context.Context
    27  	ContextDone func()
    28  	TsFunc      func() time.Time
    29  }
    30  
    31  // newRenderTestRunner returns a test runner for render
    32  func newRenderTestRunner(t *testing.T, testName string) *renderTestRunner {
    33  	ctx, done := context.WithCancel(context.Background())
    34  	defer done()
    35  
    36  	r := renderTestRunner{}
    37  	r.Context, r.ContextDone = context.WithCancel(context.Background())
    38  
    39  	// To keep hashes consistent, artificially specify the timestamp by overriding
    40  	// the dsfs.Timestamp func
    41  	r.TsFunc = dsfs.Timestamp
    42  	dsfs.Timestamp = func() time.Time { return time.Time{} }
    43  
    44  	var err error
    45  	r.Repo, err = testrepo.NewTestRepo()
    46  	if err != nil {
    47  		panic(err)
    48  	}
    49  
    50  	r.Node, err = p2p.NewQriNode(r.Repo, testcfg.DefaultP2PForTesting(), event.NilBus, nil)
    51  	if err != nil {
    52  		panic(err)
    53  	}
    54  	r.Instance = NewInstanceFromConfigAndNode(ctx, testcfg.DefaultConfigForTesting(), r.Node)
    55  
    56  	return &r
    57  }
    58  
    59  // Delete cleans up after the test is done
    60  func (r *renderTestRunner) Delete() {
    61  	r.ContextDone()
    62  	dsfs.Timestamp = r.TsFunc
    63  }
    64  
    65  // Save saves a version of the dataset with a body
    66  func (r *renderTestRunner) Save(ref string, ds *dataset.Dataset, bodyPath string) {
    67  	params := SaveParams{
    68  		Ref:      ref,
    69  		Dataset:  ds,
    70  		BodyPath: bodyPath,
    71  	}
    72  	_, err := r.Instance.Dataset().Save(r.Context, &params)
    73  	if err != nil {
    74  		panic(err)
    75  	}
    76  }
    77  
    78  func TestRenderViz(t *testing.T) {
    79  	ctx, done := context.WithCancel(context.Background())
    80  	defer done()
    81  
    82  	// set Default Template to something easier to work with, then
    83  	// cleanup when test completes
    84  	prevDefaultTemplate := base.DefaultTemplate
    85  	base.DefaultTemplate = `<html><h1>{{.Peername}}/{{.Name}}</h1></html>`
    86  	defer func() { base.DefaultTemplate = prevDefaultTemplate }()
    87  
    88  	cases := []struct {
    89  		description string
    90  		params      *RenderParams
    91  		expect      []byte
    92  		err         string
    93  	}{
    94  		{"invalid ref",
    95  			&RenderParams{
    96  				Ref:      "foo/invalid_ref",
    97  				Selector: "viz",
    98  			}, nil, "reference not found"},
    99  		{"template override just title",
   100  			&RenderParams{
   101  				Ref:      "me/movies",
   102  				Template: []byte("{{ .Meta.Title }}"),
   103  				Selector: "viz",
   104  			}, []byte("example movie data"), ""},
   105  		{"override with invalid template",
   106  			&RenderParams{
   107  				Ref:      "me/movies",
   108  				Template: []byte("{{ .BadTemplate }}"),
   109  				Selector: "viz",
   110  			}, nil, `template: index.html:1:3: executing "index.html" at <.BadTemplate>: can't evaluate field BadTemplate in type *dataset.Dataset`},
   111  		{"override with corrupt template",
   112  			&RenderParams{
   113  				Ref:      "me/movies",
   114  				Template: []byte("{{ .BadTemplateBooPlzFail"),
   115  				Selector: "viz",
   116  			}, nil, `parsing template: template: index.html:1: unclosed action`},
   117  		{"default template",
   118  			&RenderParams{
   119  				Ref:      "me/movies",
   120  				Selector: "viz",
   121  			}, []byte("<html><h1>peer/movies</h1></html>"), ""},
   122  		{"alternate dataset default template",
   123  			&RenderParams{
   124  				Ref:      "me/sitemap",
   125  				Selector: "viz",
   126  			}, []byte("<html><h1>peer/sitemap</h1></html>"), ""},
   127  	}
   128  
   129  	tr, err := testrepo.NewTestRepo()
   130  	if err != nil {
   131  		t.Errorf("error allocating test repo: %s", err.Error())
   132  		return
   133  	}
   134  	node, err := p2p.NewQriNode(tr, testcfg.DefaultP2PForTesting(), event.NilBus, nil)
   135  	if err != nil {
   136  		t.Fatal(err.Error())
   137  	}
   138  
   139  	inst := NewInstanceFromConfigAndNode(ctx, testcfg.DefaultConfigForTesting(), node)
   140  
   141  	for i, c := range cases {
   142  		got, err := inst.Dataset().Render(ctx, c.params)
   143  		if !(err == nil && c.err == "" || err != nil && err.Error() == c.err) {
   144  			t.Errorf("case %d %s error mismatch. expected: '%s', got: '%s'", i, c.description, c.err, err)
   145  			return
   146  		}
   147  
   148  		if diff := cmp.Diff(string(got), string(c.expect)); diff != "" {
   149  			t.Errorf("case %d result mismatch. (-want +got):\n%s", i, c.description)
   150  		}
   151  	}
   152  }
   153  
   154  // Test that render with a readme returns an html string
   155  func TestRenderReadme(t *testing.T) {
   156  	runner := newRenderTestRunner(t, "render_readme")
   157  	defer runner.Delete()
   158  
   159  	ctx := context.TODO()
   160  
   161  	runner.Save(
   162  		"me/my_dataset",
   163  		&dataset.Dataset{
   164  			Readme: &dataset.Readme{
   165  				Text: "# hi\n\nhello\n",
   166  			},
   167  		},
   168  		"testdata/jobs_by_automation/body.csv")
   169  
   170  	params := RenderParams{
   171  		Ref:      "peer/my_dataset",
   172  		Format:   "html",
   173  		Selector: "readme",
   174  	}
   175  	text, err := runner.Instance.Dataset().Render(ctx, &params)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  
   180  	expect := "<h1>hi</h1>\n\n<p>hello</p>\n"
   181  	if diff := cmp.Diff(expect, string(text)); diff != "" {
   182  		t.Errorf("response mismatch (-want +got):\n%s", diff)
   183  	}
   184  
   185  	params = RenderParams{
   186  		Dataset: &dataset.Dataset{
   187  			Readme: &dataset.Readme{
   188  				Text: "# hi\n\nhello",
   189  			},
   190  		},
   191  		Selector: "readme",
   192  	}
   193  	text, err = runner.Instance.Dataset().Render(ctx, &params)
   194  	if err != nil {
   195  		t.Errorf("dynamic dataset render error: %s", err)
   196  	}
   197  
   198  	if diff := cmp.Diff(expect, string(text)); diff != "" {
   199  		t.Errorf("dynamic dataset render response mismatch (-want +got):\n%s", diff)
   200  	}
   201  
   202  	params = RenderParams{
   203  		Ref: "foo/bar",
   204  		Dataset: &dataset.Dataset{
   205  			Readme: &dataset.Readme{
   206  				Text: "# hi\n\nhello",
   207  			},
   208  		},
   209  		Selector: "readme",
   210  	}
   211  	text, err = runner.Instance.Dataset().Render(ctx, &params)
   212  	if err == nil {
   213  		t.Errorf("expected RenderReadme with both ref & dataset to error")
   214  	}
   215  }
   216  
   217  func TestRenderValidationFailure(t *testing.T) {
   218  	runner := newRenderTestRunner(t, "render_readme")
   219  	defer runner.Delete()
   220  
   221  	params := RenderParams{
   222  		Ref:      "peer/my_dataset",
   223  		Dataset:  &dataset.Dataset{},
   224  		Format:   "html",
   225  		Selector: "viz",
   226  	}
   227  	_, err := runner.Instance.Dataset().Render(runner.Context, &params)
   228  	if err == nil {
   229  		t.Fatal("expected error, got nil")
   230  	}
   231  
   232  	expect := "cannot provide both a reference and a dataset to render"
   233  	if diff := cmp.Diff(expect, err.Error()); diff != "" {
   234  		t.Errorf("err mismatch (-want +got):\n%s", diff)
   235  	}
   236  
   237  	params = RenderParams{}
   238  	_, err = runner.Instance.Dataset().Render(runner.Context, &params)
   239  	if !errors.Is(dsref.ErrEmptyRef, err) {
   240  		t.Errorf("err mismatch, expected %q, got %q", dsref.ErrEmptyRef, err)
   241  	}
   242  
   243  	params = RenderParams{
   244  		Ref: "peer/my_dataset",
   245  	}
   246  	_, err = runner.Instance.Dataset().Render(runner.Context, &params)
   247  	if err == nil {
   248  		t.Fatal("expected error, got nil")
   249  	}
   250  	expect = "selector must be one of 'viz' or 'readme'"
   251  	if diff := cmp.Diff(expect, err.Error()); diff != "" {
   252  		t.Errorf("err mismatch (-want +got):\n%s", diff)
   253  	}
   254  }