DataApi.utils.test.ts167 lines · main
1import { describe, expect, it } from 'vitest'
2
3import { buildEntityMaps, getApiEndpoint } from './DataApi.utils'
4import type { ProjectJsonSchemaPaths } from '@/data/docs/project-json-schema-query'
5import type { LoadBalancer } from '@/data/read-replicas/load-balancers-query'
6import type { Database } from '@/data/read-replicas/replicas-query'
7
8const makeDatabase = (
9 identifier: string,
10 restUrl: string
11): Pick<Database, 'identifier' | 'restUrl'> => ({ identifier, restUrl })
12
13const makeLoadBalancer = (endpoint: string): Pick<LoadBalancer, 'endpoint'> => ({ endpoint })
14
15describe('getApiEndpoint', () => {
16 it('returns custom domain URL with /rest/v1/ when custom domain is active and primary database is selected', () => {
17 expect(
18 getApiEndpoint({
19 selectedDatabaseId: 'project-ref',
20 projectRef: 'project-ref',
21 resolvedEndpoint: 'https://api.example.com',
22 loadBalancers: undefined,
23 selectedDatabase: makeDatabase(
24 'project-ref',
25 'https://project-ref.supabase.co/rest/v1/'
26 ) as Database,
27 })
28 ).toBe('https://api.example.com/rest/v1/')
29 })
30
31 it('returns database restUrl when custom domain is active but a replica is selected', () => {
32 expect(
33 getApiEndpoint({
34 selectedDatabaseId: 'replica-1',
35 projectRef: 'project-ref',
36 resolvedEndpoint: 'https://api.example.com',
37 loadBalancers: undefined,
38 selectedDatabase: makeDatabase(
39 'replica-1',
40 'https://replica-1.supabase.co/rest/v1/'
41 ) as Database,
42 })
43 ).toBe('https://replica-1.supabase.co/rest/v1/')
44 })
45
46 it('normalizes a replica restUrl without a trailing slash', () => {
47 expect(
48 getApiEndpoint({
49 selectedDatabaseId: 'replica-1',
50 projectRef: 'project-ref',
51 resolvedEndpoint: undefined,
52 loadBalancers: undefined,
53 selectedDatabase: makeDatabase(
54 'replica-1',
55 'https://replica-1.supabase.co/rest/v1'
56 ) as Database,
57 })
58 ).toBe('https://replica-1.supabase.co/rest/v1/')
59 })
60
61 it('returns load balancer endpoint with /rest/v1/ when load balancer is selected', () => {
62 expect(
63 getApiEndpoint({
64 selectedDatabaseId: 'load-balancer',
65 projectRef: 'project-ref',
66 resolvedEndpoint: 'https://project-ref.supabase.co',
67 loadBalancers: [makeLoadBalancer('https://lb.supabase.co') as LoadBalancer],
68 selectedDatabase: undefined,
69 })
70 ).toBe('https://lb.supabase.co/rest/v1/')
71 })
72
73 it('returns empty string when load balancer is selected but none exist', () => {
74 expect(
75 getApiEndpoint({
76 selectedDatabaseId: 'load-balancer',
77 projectRef: 'project-ref',
78 resolvedEndpoint: 'https://project-ref.supabase.co',
79 loadBalancers: undefined,
80 selectedDatabase: undefined,
81 })
82 ).toBe('')
83 })
84
85 it('returns database restUrl for a replica database selection', () => {
86 expect(
87 getApiEndpoint({
88 selectedDatabaseId: 'replica-2',
89 projectRef: 'project-ref',
90 resolvedEndpoint: 'https://project-ref.supabase.co',
91 loadBalancers: undefined,
92 selectedDatabase: makeDatabase(
93 'replica-2',
94 'https://replica-2.supabase.co/rest/v1/'
95 ) as Database,
96 })
97 ).toBe('https://replica-2.supabase.co/rest/v1/')
98 })
99})
100
101describe('buildEntityMaps', () => {
102 it('returns empty maps for undefined paths', () => {
103 expect(buildEntityMaps(undefined)).toEqual({ resources: {}, rpcs: {} })
104 })
105
106 it('returns empty maps for empty paths', () => {
107 expect(buildEntityMaps({})).toEqual({ resources: {}, rpcs: {} })
108 })
109
110 it('skips the root path', () => {
111 const paths: ProjectJsonSchemaPaths = {
112 '/': { get: {} as ProjectJsonSchemaPaths[string]['get'] },
113 }
114 expect(buildEntityMaps(paths)).toEqual({ resources: {}, rpcs: {} })
115 })
116
117 it('classifies non-rpc paths as resources', () => {
118 const paths: ProjectJsonSchemaPaths = {
119 '/users': { get: {} as ProjectJsonSchemaPaths[string]['get'] },
120 '/posts': { get: {} as ProjectJsonSchemaPaths[string]['get'] },
121 }
122 const result = buildEntityMaps(paths)
123 expect(Object.keys(result.resources)).toEqual(['users', 'posts'])
124 expect(result.rpcs).toEqual({})
125 })
126
127 it('classifies rpc/ paths as rpcs', () => {
128 const paths: ProjectJsonSchemaPaths = {
129 '/rpc/my_function': { post: {} as ProjectJsonSchemaPaths[string]['post'] },
130 }
131 const result = buildEntityMaps(paths)
132 expect(result.resources).toEqual({})
133 expect(Object.keys(result.rpcs)).toEqual(['my_function'])
134 })
135
136 it('enriches entities with displayName and camelCase', () => {
137 const paths: ProjectJsonSchemaPaths = {
138 '/user_profiles': { get: {} as ProjectJsonSchemaPaths[string]['get'] },
139 '/rpc/get_user_count': { post: {} as ProjectJsonSchemaPaths[string]['post'] },
140 }
141 const result = buildEntityMaps(paths)
142
143 expect(result.resources['user_profiles']).toEqual({
144 id: 'user_profiles',
145 displayName: 'user profiles',
146 camelCase: 'userProfiles',
147 })
148 expect(result.rpcs['get_user_count']).toEqual({
149 id: 'get_user_count',
150 displayName: 'get user count',
151 camelCase: 'getUserCount',
152 })
153 })
154
155 it('handles mixed resources and rpcs', () => {
156 const paths: ProjectJsonSchemaPaths = {
157 '/': { get: {} as ProjectJsonSchemaPaths[string]['get'] },
158 '/users': { get: {} as ProjectJsonSchemaPaths[string]['get'] },
159 '/rpc/hello': { post: {} as ProjectJsonSchemaPaths[string]['post'] },
160 '/posts': { get: {} as ProjectJsonSchemaPaths[string]['get'] },
161 '/rpc/goodbye': { post: {} as ProjectJsonSchemaPaths[string]['post'] },
162 }
163 const result = buildEntityMaps(paths)
164 expect(Object.keys(result.resources)).toEqual(['users', 'posts'])
165 expect(Object.keys(result.rpcs)).toEqual(['hello', 'goodbye'])
166 })
167})