Skip to content

Commit eac4e2d

Browse files
🧪 [testing improvement] Missing test for compiled objects (proto.preOps) (#46)
* 🧪 test: add coverage for compiled objects and proto.preOps - Added comprehensive tests for compiled objects in `test/compiled_objects.test.js`. - Covered `proto.preOps` and `proto.queries` handling for both array and string types. - Verified fallback mechanisms and monkeypatching logic. - Increased line coverage of `index.js` to 100%. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> * fix: standardize string quotes in compiled objects tests * refactor: simplify monkeypatched queries tests with parameterized cases --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
1 parent 804e997 commit eac4e2d

1 file changed

Lines changed: 272 additions & 0 deletions

File tree

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
const { autoAssignActions } = require('../index')
2+
3+
describe('Compiled Objects and Edge Cases', () => {
4+
let originalDataform
5+
6+
beforeEach(() => {
7+
// Save original global state
8+
originalDataform = global.dataform
9+
10+
// Reset global state
11+
global.dataform = {
12+
actions: [],
13+
projectConfig: {
14+
defaultDatabase: 'test-project',
15+
defaultSchema: 'test-schema'
16+
}
17+
}
18+
})
19+
20+
afterEach(() => {
21+
// Restore original global state
22+
global.dataform = originalDataform
23+
})
24+
25+
const config = [
26+
{
27+
tag: 'test',
28+
reservation: 'projects/test/locations/US/reservations/prod',
29+
actions: ['test-project.test-schema.target_action']
30+
}
31+
]
32+
33+
test('should apply reservation to compiled object via proto.preOps (array)', () => {
34+
const action = {
35+
type: 'table',
36+
target: {
37+
database: 'test-project',
38+
schema: 'test-schema',
39+
name: 'target_action'
40+
},
41+
preOps: [] // This will be treated as proto.preOps because action.proto is undefined
42+
}
43+
global.dataform.actions.push(action)
44+
45+
autoAssignActions(config)
46+
47+
expect(action.preOps).toContain('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
48+
})
49+
50+
test('should initialize proto.preOps if it does not exist', () => {
51+
const action = {
52+
type: 'table',
53+
target: {
54+
database: 'test-project',
55+
schema: 'test-schema',
56+
name: 'target_action'
57+
}
58+
// preOps is missing
59+
}
60+
global.dataform.actions.push(action)
61+
62+
autoAssignActions(config)
63+
64+
expect(action.preOps).toBeDefined()
65+
expect(Array.isArray(action.preOps)).toBe(true)
66+
expect(action.preOps).toContain('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
67+
})
68+
69+
test('should handle proto.preOps as a string', () => {
70+
const action = {
71+
type: 'table',
72+
target: {
73+
database: 'test-project',
74+
schema: 'test-schema',
75+
name: 'target_action'
76+
},
77+
preOps: 'SELECT 1;'
78+
}
79+
global.dataform.actions.push(action)
80+
81+
autoAssignActions(config)
82+
83+
expect(Array.isArray(action.preOps)).toBe(true)
84+
expect(action.preOps[0]).toBe('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
85+
expect(action.preOps[1]).toBe('SELECT 1;')
86+
})
87+
88+
test('should apply reservation to compiled operation via proto.queries (array)', () => {
89+
const action = {
90+
queries: ['SELECT * FROM table'],
91+
target: {
92+
database: 'test-project',
93+
schema: 'test-schema',
94+
name: 'target_action'
95+
}
96+
}
97+
global.dataform.actions.push(action)
98+
99+
autoAssignActions(config)
100+
101+
expect(action.queries).toContain('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
102+
expect(action.queries[0]).toBe('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
103+
})
104+
105+
test('should handle proto.queries as a string', () => {
106+
const action = {
107+
queries: 'SELECT * FROM table',
108+
target: {
109+
database: 'test-project',
110+
schema: 'test-schema',
111+
name: 'target_action'
112+
}
113+
}
114+
global.dataform.actions.push(action)
115+
116+
autoAssignActions(config)
117+
118+
expect(Array.isArray(action.queries)).toBe(true)
119+
expect(action.queries[0]).toBe('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
120+
expect(action.queries[1]).toBe('SELECT * FROM table')
121+
})
122+
123+
test('should fallback to action.preOps() function if hasType is true but proto.preOps is not an array/string', () => {
124+
const preOpsMock = jest.fn()
125+
const action = {
126+
type: 'table',
127+
target: {
128+
database: 'test-project',
129+
schema: 'test-schema',
130+
name: 'target_action'
131+
},
132+
preOps: preOpsMock // This is a function
133+
}
134+
global.dataform.actions.push(action)
135+
136+
autoAssignActions(config)
137+
138+
expect(preOpsMock).toHaveBeenCalledWith('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
139+
})
140+
141+
test('should fallback to action.preOps() function if no other method matches (Fallback 5)', () => {
142+
const preOpsMock = jest.fn()
143+
const action = {
144+
// No type, no queries, no contextable fields
145+
target: {
146+
database: 'test-project',
147+
schema: 'test-schema',
148+
name: 'target_action'
149+
},
150+
preOps: preOpsMock
151+
}
152+
global.dataform.actions.push(action)
153+
154+
autoAssignActions(config)
155+
156+
expect(preOpsMock).toHaveBeenCalledWith('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
157+
})
158+
159+
test('should handle contextableQueries as a string', () => {
160+
const action = {
161+
contextableQueries: 'SELECT 1',
162+
target: {
163+
database: 'test-project',
164+
schema: 'test-schema',
165+
name: 'target_action'
166+
}
167+
}
168+
global.dataform.actions.push(action)
169+
170+
autoAssignActions(config)
171+
172+
expect(Array.isArray(action.contextableQueries)).toBe(true)
173+
expect(action.contextableQueries[0]).toBe('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
174+
})
175+
176+
test('should handle contextablePreOps as a string', () => {
177+
const action = {
178+
type: 'table',
179+
contextablePreOps: 'SELECT 1',
180+
target: {
181+
database: 'test-project',
182+
schema: 'test-schema',
183+
name: 'target_action'
184+
}
185+
}
186+
global.dataform.actions.push(action)
187+
188+
autoAssignActions(config)
189+
190+
expect(Array.isArray(action.contextablePreOps)).toBe(true)
191+
expect(action.contextablePreOps[0]).toBe('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
192+
})
193+
194+
test.each([
195+
{
196+
name: 'function returning string',
197+
input: () => 'SELECT 1',
198+
validator: (result) => {
199+
expect(result).toContain('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
200+
expect(result).toContain('SELECT 1')
201+
}
202+
},
203+
{
204+
name: 'function returning array',
205+
input: () => ['SELECT 1', 'SELECT 2'],
206+
validator: (result) => {
207+
expect(result[0]).toBe('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
208+
expect(result).toHaveLength(3)
209+
}
210+
},
211+
{
212+
name: 'function returning other types',
213+
input: () => null,
214+
validator: (result) => {
215+
expect(result).toBe(null)
216+
}
217+
},
218+
{
219+
name: 'array',
220+
input: ['SELECT 1'],
221+
validator: (result) => {
222+
expect(result[0]).toBe('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
223+
}
224+
}
225+
])('should handle monkeypatched queries with $name', ({ input, validator }) => {
226+
const action = {
227+
queries: function(q) { this.resolvedQueries = q; return this },
228+
proto: {
229+
queries: [],
230+
target: { database: 'test-project', schema: 'test-schema', name: 'target_action' }
231+
}
232+
}
233+
global.dataform.actions.push(action)
234+
235+
autoAssignActions(config)
236+
237+
action.queries(input)
238+
239+
// For arrays, `action.queries()` directly assigns resolvedQueries if not wrapped,
240+
// but the monkeypatch will wrap them. We extract the resolved value for validation.
241+
// If input is an array, it's wrapped and immediately modifies `resolvedQueries` via the original function.
242+
// Our action mock just sets `this.resolvedQueries = q`.
243+
// So if the patched function returns, it calls the original function.
244+
// Let's resolve what to test:
245+
if (typeof input === 'function') {
246+
const wrappedFn = action.resolvedQueries
247+
expect(typeof wrappedFn).toBe('function')
248+
const result = wrappedFn({})
249+
validator(result)
250+
} else {
251+
validator(action.resolvedQueries)
252+
}
253+
})
254+
255+
test('should intercept sqlxAction', () => {
256+
const sqlxActionMock = jest.fn(() => {
257+
global.dataform.actions.push({
258+
type: 'table',
259+
target: { database: 'test-project', schema: 'test-schema', name: 'target_action' },
260+
preOps: []
261+
})
262+
})
263+
global.dataform.sqlxAction = sqlxActionMock
264+
265+
autoAssignActions(config)
266+
267+
global.dataform.sqlxAction()
268+
269+
const action = global.dataform.actions[0]
270+
expect(action.preOps).toContain('SET @@reservation=\'projects/test/locations/US/reservations/prod\';')
271+
})
272+
})

0 commit comments

Comments
 (0)