~bigbes/sr-ht-dolt

ref: 65be341fc575daaf7f1093b698b40193bf75ab93 sr-ht-dolt/web/views_test.go -rw-r--r-- 6.8 KiB
65be341f — Eugene Blikh db: read a database's timestamps out of the store 3 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package web

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"testing"

	"sourcecraft.dev/bigbes/sr-ht-ecore/pages"

	"sourcecraft.dev/bigbes/sr-ht-dolt/browse"
	"sourcecraft.dev/bigbes/sr-ht-dolt/core"
)

// dummyView is a test-only View that fingerprints repos containing a table
// named "issues". It renders through the shared error page, which is the one
// page every service's set is guaranteed to carry and the only one that asks
// nothing of the envelope beyond the .Data field every view already has. Its
// Build therefore returns a pages.ErrorData. It records whether Build ran.
type dummyView struct {
	built bool
}

func (d *dummyView) Name() string     { return "issues" }
func (d *dummyView) Label() string    { return "Issues" }
func (d *dummyView) Template() string { return pages.ErrorPage + ".html" }

func (d *dummyView) Applies(tables []browse.TableInfo) bool {
	for _, t := range tables {
		if t.Name == "issues" {
			return true
		}
	}
	return false
}

func (d *dummyView) Build(_ context.Context, sess BrowseSession, _ *core.Repo, ref string, _ url.Values) (any, error) {
	d.built = true
	// Pull data through the BrowseSession surface only, mirroring how a real
	// view works, then hand the page the payload it reads.
	tables, err := sess.Tables(context.Background(), ref)
	if err != nil {
		return nil, err
	}
	return pages.Error(http.StatusOK, fmt.Sprintf("%d tables", len(tables))), nil
}

func issueTables() []browse.TableInfo {
	return []browse.TableInfo{
		{Name: "issues", Columns: []browse.ColumnInfo{{Name: "id", PrimaryKey: true}}, RowCount: 2},
		{Name: "labels", RowCount: 5},
	}
}

func TestApplicableViewsFilters(t *testing.T) {
	v := &dummyView{}
	views := []View{v}

	got := applicableViews(views, issueTables())
	if len(got) != 1 || got[0] != v {
		t.Fatalf("applicableViews should select the matching view; got %v", got)
	}

	// No "issues" table → no applicable views.
	none := applicableViews(views, []browse.TableInfo{{Name: "widgets"}})
	if len(none) != 0 {
		t.Fatalf("applicableViews should filter out non-matching views; got %v", none)
	}
}

func TestApplicableViewsPreservesOrder(t *testing.T) {
	a := &dummyView{}
	// A second always-applies view to check registration order is preserved.
	b := alwaysView{}
	got := applicableViews([]View{b, a}, issueTables())
	if len(got) != 2 || got[0] != View(b) || got[1] != View(a) {
		t.Fatalf("applicableViews should preserve order; got %v", got)
	}
}

// alwaysView is a trivial always-applicable view used to check ordering.
type alwaysView struct{}

func (alwaysView) Name() string                    { return "always" }
func (alwaysView) Label() string                   { return "Always" }
func (alwaysView) Template() string                { return pages.ErrorPage + ".html" }
func (alwaysView) Applies([]browse.TableInfo) bool { return true }
func (alwaysView) Build(context.Context, BrowseSession, *core.Repo, string, url.Values) (any, error) {
	return pages.Error(http.StatusOK, ""), nil
}

func TestHandleViewSelectedAndBuilt(t *testing.T) {
	h := newHarness(t)
	h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
	h.browse.sess = &fakeSession{
		branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
		tables:   issueTables(),
	}

	dv := &dummyView{}
	// Inject the view directly on the app, without polluting the global registry.
	setViews(t, h, dv)

	rec := h.do("GET", "/~alice/db/view/issues", nil, nil)
	// A 200 (rather than the 404 an unknown/non-applicable view returns) proves
	// the view was selected and its Template rendered; dv.built proves Build ran.
	if rec.Code != http.StatusOK {
		t.Fatalf("view page: got %d, want 200; body=%s", rec.Code, rec.Body.String())
	}
	if !dv.built {
		t.Fatalf("view.Build was not called")
	}
	// The view's Label flows into the page <title> via the envelope's basePage,
	// so its presence confirms this view (not a fallback) produced the response.
	if !strings.Contains(rec.Body.String(), "Issues") {
		t.Fatalf("view page missing view label in chrome; body=%s", rec.Body.String())
	}
}

func TestHandleViewUnknownSlugIs404(t *testing.T) {
	h := newHarness(t)
	h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
	h.browse.sess = &fakeSession{
		branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
		tables:   issueTables(),
	}
	setViews(t, h, &dummyView{})

	rec := h.do("GET", "/~alice/db/view/nope", nil, nil)
	if rec.Code != http.StatusNotFound {
		t.Fatalf("unknown view slug: got %d, want 404", rec.Code)
	}
}

func TestHandleViewNotApplicableIs404(t *testing.T) {
	h := newHarness(t)
	h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
	// Tables lack "issues", so dummyView.Applies is false even though the slug matches.
	h.browse.sess = &fakeSession{
		branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
		tables:   []browse.TableInfo{{Name: "widgets"}},
	}
	setViews(t, h, &dummyView{})

	rec := h.do("GET", "/~alice/db/view/issues", nil, nil)
	if rec.Code != http.StatusNotFound {
		t.Fatalf("non-applicable view: got %d, want 404", rec.Code)
	}
}

func TestOverviewShowsViewTabs(t *testing.T) {
	h := newHarness(t)
	h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
	h.browse.sess = &fakeSession{
		branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
		tables:   issueTables(),
	}
	setViews(t, h, &dummyView{})

	rec := h.do("GET", "/~alice/db", nil, nil)
	if rec.Code != http.StatusOK {
		t.Fatalf("overview: got %d, want 200", rec.Code)
	}
	body := rec.Body.String()
	if !strings.Contains(body, "/~alice/db/view/issues") {
		t.Fatalf("overview missing view tab link; body=%s", body)
	}
	if !strings.Contains(body, "nav-tabs") {
		t.Fatalf("overview missing tab bar; body=%s", body)
	}
	// The bar carries three tabs in order: Overview, Tables, then the view.
	iOverview := strings.Index(body, ">Overview<")
	iTables := strings.Index(body, ">Tables<")
	iView := strings.Index(body, "/~alice/db/view/issues")
	if iOverview < 0 || iTables < 0 {
		t.Fatalf("overview missing Overview/Tables tabs; body=%s", body)
	}
	if !(iOverview < iTables && iTables < iView) {
		t.Fatalf("tab order should be Overview < Tables < view; got %d, %d, %d", iOverview, iTables, iView)
	}
	// The active tab on the overview is Overview, not Tables.
	if !regexp.MustCompile(`nav-link active"[^>]*>Overview<`).MatchString(body) {
		t.Fatalf("overview tab should be active on the overview page; body=%s", body)
	}
}

// setViews overrides the app's view snapshot without touching the global
// registry, so view routing/overview tabs can be exercised with a controlled
// set. The harness retains the *app the router is mounted on.
func setViews(t *testing.T, h *harness, vs ...View) {
	t.Helper()
	h.app.views = vs
}