~bigbes/sr-ht-dolt

ref: 9660c7204a2b9500e56c7bb2ff47316d50d5fc99 sr-ht-dolt/db/db_test.go -rw-r--r-- 4.1 KiB
9660c720 — Eugene Blikh pages: read forms through FormValues 9 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
package db

import (
	"context"
	"crypto/rand"
	"database/sql"
	"encoding/hex"
	"net/url"
	"os"
	"strings"
	"testing"
	"time"

	_ "github.com/lib/pq"

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

// testEnv names the DSN env var that gates the Postgres-backed tests. When
// unset, every test in this package skips with a clear message; the package
// still compiles and its skip path is exercised.
const testEnv = "DOLTSRHT_TEST_PG"

// newTestStore connects to the Postgres pointed at by DOLTSRHT_TEST_PG, creates
// an isolated scratch schema (doltsrht_test_<random>), applies schema.sql into
// it, and returns a Store bound to a pool whose search_path is that schema. The
// returned cleanup drops the schema and closes the pools. If the env var is
// unset the test is skipped.
//
// Isolation is achieved with a per-test schema rather than a whole database so
// no admin/CREATE DATABASE privilege is required and cleanup is a single
// DROP SCHEMA ... CASCADE. The scratch pool routes every connection to that
// schema via lib/pq's `options=-c search_path=...` startup parameter.
func newTestStore(t *testing.T) (*Store, *sql.DB, func()) {
	t.Helper()
	base := os.Getenv(testEnv)
	if base == "" {
		t.Skipf("%s not set; skipping Postgres-backed test (set it to a DSN to run)", testEnv)
	}

	admin, err := sql.Open("postgres", base)
	if err != nil {
		t.Fatalf("open admin pool: %v", err)
	}
	if err := admin.Ping(); err != nil {
		admin.Close()
		t.Fatalf("ping %s: %v", testEnv, err)
	}

	schema := "doltsrht_test_" + randToken()
	if _, err := admin.Exec(`CREATE SCHEMA "` + schema + `"`); err != nil {
		admin.Close()
		t.Fatalf("create schema %s: %v", schema, err)
	}

	scopedDSN, err := withSearchPath(base, schema)
	if err != nil {
		admin.Exec(`DROP SCHEMA "` + schema + `" CASCADE`)
		admin.Close()
		t.Fatalf("build scoped dsn: %v", err)
	}
	pool, err := sql.Open("postgres", scopedDSN)
	if err != nil {
		admin.Exec(`DROP SCHEMA "` + schema + `" CASCADE`)
		admin.Close()
		t.Fatalf("open scoped pool: %v", err)
	}

	ddl, err := os.ReadFile("../schema.sql")
	if err != nil {
		pool.Close()
		admin.Exec(`DROP SCHEMA "` + schema + `" CASCADE`)
		admin.Close()
		t.Fatalf("read schema.sql: %v", err)
	}
	if _, err := pool.Exec(string(ddl)); err != nil {
		pool.Close()
		admin.Exec(`DROP SCHEMA "` + schema + `" CASCADE`)
		admin.Close()
		t.Fatalf("apply schema.sql: %v", err)
	}

	cleanup := func() {
		pool.Close()
		if _, err := admin.Exec(`DROP SCHEMA "` + schema + `" CASCADE`); err != nil {
			t.Errorf("drop schema %s: %v", schema, err)
		}
		admin.Close()
	}
	return NewStore(pool), pool, cleanup
}

// withSearchPath returns base with a connection option that pins search_path to
// schema for every pooled connection, handling both URL and keyword DSN forms.
func withSearchPath(base, schema string) (string, error) {
	opt := "-c search_path=" + schema
	if strings.Contains(base, "://") {
		u, err := url.Parse(base)
		if err != nil {
			return "", err
		}
		q := u.Query()
		q.Set("options", opt)
		u.RawQuery = q.Encode()
		return u.String(), nil
	}
	return base + " options='" + opt + "'", nil
}

func randToken() string {
	b := make([]byte, 8)
	if _, err := rand.Read(b); err != nil {
		panic(err)
	}
	return hex.EncodeToString(b)
}

// insertUser inserts a mirror user row and returns its id.
func insertUser(t *testing.T, db *sql.DB, id int, username string, ut core.UserType) int {
	t.Helper()
	now := time.Now().UTC()
	_, err := db.Exec(`
INSERT INTO "user" (id, username, created, updated, email, user_type)
VALUES ($1, $2, $3, $3, $4, $5)`,
		id, username, now, username+"@example.test", string(ut))
	if err != nil {
		t.Fatalf("insert user %s: %v", username, err)
	}
	return id
}

// mkRepo is a convenience for CreateRepo in tests.
func mkRepo(t *testing.T, s *Store, ctx context.Context, ownerID int, ownerName, name string, vis core.Visibility) *core.Repo {
	t.Helper()
	repo, err := s.CreateRepo(ctx, &core.Repo{
		Name:       name,
		OwnerID:    ownerID,
		OwnerName:  ownerName,
		Path:       "/var/lib/dolt/~" + ownerName + "/" + name,
		Visibility: vis,
	})
	if err != nil {
		t.Fatalf("create repo %s: %v", name, err)
	}
	return repo
}