Skip to content

Commit 1fc7d68

Browse files
authored
Merge pull request #12 from launchql/dev/ci-supabase-cli-fix
test fix 1
2 parents 328f4b4 + b88816a commit 1fc7d68

File tree

7 files changed

+325
-223
lines changed

7 files changed

+325
-223
lines changed

.github/workflows/ci.yml

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,20 @@ on:
88
jobs:
99
test:
1010
runs-on: ubuntu-latest
11-
container: pyramation/node-sqitch:20.12.0
1211
continue-on-error: true
1312
strategy:
1413
fail-fast: false
1514
matrix:
1615
package:
17-
- supabase
1816
- rls-demo
17+
- supabase
1918

2019
env:
21-
PGHOST: pg_db
22-
PGPORT: 5432
20+
PGHOST: 127.0.0.1
21+
PGPORT: 54322
2322
PGUSER: supabase_admin
2423
PGPASSWORD: postgres
25-
26-
services:
27-
pg_db:
28-
image: public.ecr.aws/supabase/postgres:17.6.1.029
29-
env:
30-
POSTGRES_USER: supabase_admin
31-
POSTGRES_PASSWORD: postgres
32-
options: >-
33-
--health-cmd pg_isready
34-
--health-interval 10s
35-
--health-timeout 5s
36-
--health-retries 5
37-
ports:
38-
- 5432:5432
24+
DATABASE_URL: "postgresql://postgres:[email protected]:54322/postgres"
3925

4026
steps:
4127
- name: Configure Git (for tests)
@@ -46,6 +32,38 @@ jobs:
4632
- name: Checkout
4733
uses: actions/checkout@v4
4834

35+
- name: Setup Supabase CLI
36+
uses: supabase/setup-cli@v1
37+
with:
38+
version: latest
39+
40+
- name: Initialize Supabase (if needed)
41+
run: test -d supabase || supabase init
42+
43+
- name: Start Supabase stack
44+
run: supabase start
45+
46+
- name: Show Supabase status
47+
run: supabase status
48+
49+
- name: Install Postgres client
50+
run: |
51+
sudo apt-get update
52+
sudo apt-get install -y postgresql-client
53+
54+
- name: Wait for Postgres
55+
run: |
56+
for i in {1..60}; do
57+
if pg_isready -h 127.0.0.1 -p 54322 -U postgres; then
58+
exit 0
59+
fi
60+
sleep 2
61+
done
62+
echo "postgres not ready in time"
63+
docker ps
64+
supabase status
65+
exit 1
66+
4967
- name: Enable corepack and pnpm
5068
run: |
5169
corepack enable
@@ -57,7 +75,7 @@ jobs:
5775
run: pnpm install
5876

5977
- name: Install LaunchQL CLI globally
60-
run: npm install -g @launchql/[email protected].1
78+
run: npm install -g @launchql/[email protected].3
6179

6280
- name: Build
6381
run: pnpm -r build
@@ -67,9 +85,9 @@ jobs:
6785
# lql admin-users bootstrap --yes
6886
# lql admin-users add --test --yes
6987
# env:
70-
# PGHOST: pg_db
71-
# PGPORT: 5432
72-
# PGUSER: supabase_admin
88+
# PGHOST: 127.0.0.1
89+
# PGPORT: 54322
90+
# PGUSER: postgres
7391
# PGPASSWORD: postgres
7492

7593
- name: Test ${{ matrix.package }}

docs/USAGE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
```
3+
npx supabase init
4+
npx supabase start
5+
```
6+
7+
```
8+
pnpm install
9+
cd packages/base32
10+
export PGPORT=54322
11+
export PGHOST=localhost
12+
export PGUSER=postgres
13+
export PGPASSWORD=postgres
14+
15+
pnpm test
16+
```

docs/img/logos.svg

Lines changed: 16 additions & 0 deletions
Loading

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
"@types/node": "^22.10.4",
3030
"@typescript-eslint/eslint-plugin": "^8.0.0",
3131
"@typescript-eslint/parser": "^8.0.0",
32-
"@launchql/logger": "^1.1.0",
3332
"copyfiles": "^2.4.1",
3433
"cpy-cli": "^6.0.0",
3534
"eslint-config-prettier": "^9.1.0",
@@ -39,7 +38,7 @@
3938
"jest-in-case": "^1.0.2",
4039
"jest": "^29.6.2",
4140
"lerna": "^8.2.3",
42-
"pgsql-test": "^2.11.1",
41+
"pgsql-test": "^2.11.5",
4342
"prettier": "^3.0.2",
4443
"rimraf": "4.4.1",
4544
"ts-jest": "^29.1.1",

packages/rls-demo/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@
1212
"test:watch": "jest --watch"
1313
},
1414
"devDependencies": {
15-
"@launchql/cli": "^4.9.0"
15+
"@launchql/cli": "^4.12.3"
1616
}
1717
}

packages/supabase/__tests__/conditional.test.ts

Lines changed: 108 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// @ts-nocheck
12
import { getConnections, PgTestClient } from 'pgsql-test';
23

34
let pg: PgTestClient;
@@ -18,11 +19,12 @@ beforeAll(async () => {
1819
);
1920
expect(authSchemaExists[0].exists).toBe(true);
2021

21-
// grant access to auth schema for testing
22-
// grant INSERT on auth.users to service_role so we can create test users
22+
// minimal grants for tests
2323
await pg.any(
2424
`GRANT USAGE ON SCHEMA auth TO public;
25-
GRANT INSERT ON TABLE auth.users TO service_role;
25+
GRANT USAGE ON SCHEMA storage TO public;
26+
GRANT SELECT ON TABLE auth.users TO service_role;
27+
GRANT SELECT ON TABLE storage.buckets TO service_role;
2628
GRANT EXECUTE ON FUNCTION auth.uid() TO public;
2729
GRANT EXECUTE ON FUNCTION auth.role() TO public;`,
2830
[]
@@ -45,52 +47,92 @@ describe('tutorial: rls with conditional policies', () => {
4547
it('should verify rls policies can use case statements', async () => {
4648
db.setContext({ role: 'service_role' });
4749

48-
// using auth.users (real supabase table) instead of rls_test.users (fake test table)
49-
// use pg for auth.users insert since it requires superuser privileges
50+
// insert into auth.users using admin connection
5051
const user1 = await pg.one(
5152
`INSERT INTO auth.users (id, email)
5253
VALUES (gen_random_uuid(), $1)
5354
RETURNING id`,
5455
5556
);
56-
57+
// ensure auth.role() sees service_role via jwt claim
58+
db.setContext({
59+
role: 'service_role',
60+
'request.jwt.claim.role': 'service_role'
61+
});
62+
// case over email and auth.role()
63+
const rows = await db.any(
64+
`SELECT
65+
CASE WHEN email LIKE '%@example.com' THEN 'example' ELSE 'other' END AS grp,
66+
CASE WHEN auth.role() = 'service_role' THEN 'visible' ELSE 'hidden' END AS visibility
67+
FROM auth.users
68+
WHERE id = $1`,
69+
[user1.id]
70+
);
71+
expect(Array.isArray(rows)).toBe(true);
72+
expect(rows.length).toBe(1);
73+
expect(rows[0].grp).toBe('example');
74+
expect(rows[0].visibility).toBe('visible');
5775
});
5876

5977
it('should verify rls policies work with multiple conditions', async () => {
6078
db.setContext({ role: 'service_role' });
6179

62-
// using auth.users (real supabase table) instead of rls_test.users (fake test table)
63-
// use pg for auth.users insert since it requires superuser privileges
80+
// insert into auth.users using admin connection
6481
const user = await pg.one(
6582
`INSERT INTO auth.users (id, email)
6683
VALUES (gen_random_uuid(), $1)
6784
RETURNING id`,
6885
6986
);
70-
71-
87+
// ensure auth.role() sees service_role via jwt claim
88+
db.setContext({
89+
role: 'service_role',
90+
'request.jwt.claim.role': 'service_role'
91+
});
92+
// and/or combinations with auth.role()
93+
const rows = await db.any(
94+
`SELECT id
95+
FROM auth.users
96+
WHERE (email = $1 AND (auth.role() = 'service_role' OR auth.role() = 'authenticated'))
97+
AND id = $2`,
98+
['[email protected]', user.id]
99+
);
100+
expect(Array.isArray(rows)).toBe(true);
101+
expect(rows.length).toBe(1);
102+
expect(rows[0].id).toBe(user.id);
72103
});
73104

74105
it('should verify rls policies work with or conditions', async () => {
75106
db.setContext({ role: 'service_role' });
76107

77-
// using auth.users (real supabase table) instead of rls_test.users (fake test table)
78-
// use pg for auth.users insert since it requires superuser privileges
108+
// insert into auth.users using admin connection
79109
const user = await pg.one(
80110
`INSERT INTO auth.users (id, email)
81111
VALUES (gen_random_uuid(), $1)
82112
RETURNING id`,
83113
84114
);
85-
86-
115+
const user2 = await pg.one(
116+
`INSERT INTO auth.users (id, email)
117+
VALUES (gen_random_uuid(), $1)
118+
RETURNING id`,
119+
120+
);
121+
122+
const rows = await db.any(
123+
`SELECT COUNT(*)::integer AS count
124+
FROM auth.users
125+
WHERE email = $1 OR email = $2`,
126+
127+
);
128+
expect(Array.isArray(rows)).toBe(true);
129+
expect(Number(rows[0].count)).toBe(2);
87130
});
88131

89132
it('should verify rls policies work with subqueries', async () => {
90133
db.setContext({ role: 'service_role' });
91134

92-
// using auth.users (real supabase table) instead of rls_test.users (fake test table)
93-
// use pg for auth.users insert since it requires superuser privileges
135+
// insert into auth.users using admin connection
94136
const user1 = await pg.one(
95137
`INSERT INTO auth.users (id, email)
96138
VALUES (gen_random_uuid(), $1)
@@ -105,53 +147,86 @@ describe('tutorial: rls with conditional policies', () => {
105147
RETURNING id`,
106148
107149
);
108-
109-
150+
// use a subquery to resolve ids by email
151+
const viaSub = await db.any(
152+
`SELECT u.id
153+
FROM auth.users u
154+
WHERE u.id IN (SELECT id FROM auth.users WHERE email = $1)`,
155+
156+
);
157+
expect(viaSub.length).toBe(1);
158+
expect(viaSub[0].id).toBe(user1.id);
159+
160+
// correlated exists
161+
const existsRows = await db.any(
162+
`SELECT u.id
163+
FROM auth.users u
164+
WHERE EXISTS (
165+
SELECT 1 FROM auth.users i WHERE i.id = u.id AND i.email = $1
166+
)`,
167+
168+
);
169+
expect(existsRows.length).toBeGreaterThan(0);
170+
const found = existsRows.find((r: any) => r.id === user2.id);
171+
expect(Boolean(found)).toBe(true);
110172
});
111173

112174
it('should verify rls policies work with related table checks', async () => {
113175
db.setContext({ role: 'service_role' });
114176

115-
// using auth.users (real supabase table) instead of rls_test.users (fake test table)
116-
// use pg for auth.users insert since it requires superuser privileges
177+
// insert into auth.users using admin connection
117178
const user = await pg.one(
118179
`INSERT INTO auth.users (id, email)
119180
VALUES (gen_random_uuid(), $1)
120181
RETURNING id`,
121182
122183
);
123-
124-
184+
// left join storage.buckets by fk owner -> auth.users(id)
185+
const rows = await db.any(
186+
`SELECT u.id, b.id AS bucket_id
187+
FROM auth.users u
188+
LEFT JOIN storage.buckets b ON b.owner = u.id
189+
WHERE u.id = $1`,
190+
[user.id]
191+
);
192+
expect(rows.length).toBe(1);
193+
// no bucket yet, so join should be null
194+
expect(rows[0].bucket_id === null || rows[0].bucket_id === undefined).toBe(true);
125195
});
126196

127197
it('should verify rls policies work with null checks', async () => {
128198
db.setContext({ role: 'service_role' });
129199

130-
// using auth.users (real supabase table) instead of rls_test.users (fake test table)
131-
// use pg for auth.users insert since it requires superuser privileges
200+
// insert into auth.users using admin connection
132201
const user = await pg.one(
133202
`INSERT INTO auth.users (id, email)
134203
VALUES (gen_random_uuid(), $1)
135-
RETURNING id`,
136-
204+
RETURNING id, email`,
205+
[null]
137206
);
138-
139-
207+
const rows = await db.any(
208+
`SELECT id FROM auth.users WHERE id = $1 AND email IS NULL`,
209+
[user.id]
210+
);
211+
expect(rows.length).toBe(1);
140212
});
141213

142214
it('should verify rls policies work with coalesce functions', async () => {
143215
db.setContext({ role: 'service_role' });
144216

145-
// using auth.users (real supabase table) instead of rls_test.users (fake test table)
146-
// use pg for auth.users insert since it requires superuser privileges
217+
// insert into auth.users using admin connection
147218
const user = await pg.one(
148219
`INSERT INTO auth.users (id, email)
149220
VALUES (gen_random_uuid(), $1)
150221
RETURNING id`,
151-
222+
[null]
152223
);
153-
154-
224+
const rows = await db.any(
225+
`SELECT COALESCE(email, '') AS safe_email FROM auth.users WHERE id = $1`,
226+
[user.id]
227+
);
228+
expect(rows.length).toBe(1);
229+
expect(rows[0].safe_email).toBe('');
155230
});
156231
});
157232

0 commit comments

Comments
 (0)