Skip to content

Commit 5cb9101

Browse files
committed
test(mssql): cover the block param merge and the operation-to-tool map
Asserts on the merged `{ ...inputs, ...buildParams(inputs) }` the generic tool handler forwards rather than the mapper's return, since a key the mapper omits keeps its raw subBlock value through that merge. Pins the TLS toggles to their string form end to end — a switch subBlock would serialize `'false'`, which is truthy, and the route contract would coerce the user's off into on — and checks that duplicate subBlock ids agree on their seeded default.
1 parent abc79bb commit 5cb9101

1 file changed

Lines changed: 139 additions & 0 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/**
2+
* @vitest-environment node
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { MSSQLBlock } from '@/blocks/blocks/mssql'
6+
7+
/**
8+
* Every assertion here runs against `{ ...inputs, ...buildParams(inputs) }`, the
9+
* shape the generic tool handler actually forwards. A key the mapper omits is
10+
* *not* dropped by that merge — the raw subBlock value survives — so asserting
11+
* on the mapper's return alone would prove nothing about what the tool receives.
12+
*/
13+
describe('MSSQLBlock', () => {
14+
const buildParams = MSSQLBlock.tools.config.params!
15+
const selectTool = MSSQLBlock.tools.config.tool!
16+
17+
const connection = {
18+
host: 'db.example.com',
19+
port: '1433',
20+
database: 'app',
21+
username: 'app',
22+
password: 'secret',
23+
}
24+
25+
it('maps every dropdown operation onto a registered tool', () => {
26+
const operation = MSSQLBlock.subBlocks.find((subBlock) => subBlock.id === 'operation')
27+
const optionIds = operation?.options?.map((option) => option.id) ?? []
28+
29+
expect(optionIds).toHaveLength(6)
30+
expect(new Set(optionIds.map((id) => selectTool({ operation: id })))).toEqual(
31+
new Set(MSSQLBlock.tools.access)
32+
)
33+
})
34+
35+
it('rejects an operation outside the registered tool set', () => {
36+
expect(() => selectTool({ operation: 'mssql_truncate' })).toThrow(
37+
/Invalid Microsoft SQL Server operation/
38+
)
39+
})
40+
41+
/**
42+
* The TLS toggles are string enums rather than switches on purpose: a switch
43+
* subBlock serializes the *string* `'false'`, which is truthy, and the route
44+
* contract would then coerce the user's "off" into `true`. These assertions
45+
* pin the string all the way through the merge.
46+
*/
47+
it('carries the TLS toggles through as strings, never as booleans', () => {
48+
const inputs = {
49+
...connection,
50+
operation: 'query',
51+
query: 'SELECT 1',
52+
encrypt: 'disabled',
53+
trustServerCertificate: 'disabled',
54+
}
55+
const finalInputs = { ...inputs, ...buildParams(inputs) }
56+
57+
expect(finalInputs.encrypt).toBe('disabled')
58+
expect(finalInputs.trustServerCertificate).toBe('disabled')
59+
})
60+
61+
it('defaults the TLS toggles to the secure pair when the subBlocks are untouched', () => {
62+
const inputs = { ...connection, operation: 'query', query: 'SELECT 1' }
63+
const finalInputs = { ...inputs, ...buildParams(inputs) }
64+
65+
expect(finalInputs.encrypt).toBe('enabled')
66+
expect(finalInputs.trustServerCertificate).toBe('disabled')
67+
})
68+
69+
it('parses the port and a JSON data payload into their runtime types', () => {
70+
const inputs = {
71+
...connection,
72+
port: '14330',
73+
operation: 'insert',
74+
table: 'users',
75+
data: '{"name":"Jane","age":30}',
76+
}
77+
const finalInputs = { ...inputs, ...buildParams(inputs) }
78+
79+
expect(finalInputs.port).toBe(14330)
80+
expect(finalInputs.data).toEqual({ name: 'Jane', age: 30 })
81+
})
82+
83+
it('surfaces a malformed data payload as a named error rather than forwarding the string', () => {
84+
expect(() =>
85+
buildParams({ ...connection, operation: 'insert', table: 'users', data: '{not json' })
86+
).toThrow(/Invalid JSON data format/)
87+
})
88+
89+
/**
90+
* The mapper leaves `connectionTimeout` unassigned when it is blank, but the
91+
* merge means the empty subBlock string reaches the tool regardless — so the
92+
* omission is not what makes this safe. The tool's own `params.connectionTimeout ? …`
93+
* guard is, and the route contract then applies its 15000 ms default.
94+
*/
95+
it('lets a blank connectionTimeout through the merge as the raw empty string', () => {
96+
const inputs = { ...connection, operation: 'query', query: 'SELECT 1', connectionTimeout: '' }
97+
const finalInputs = { ...inputs, ...buildParams(inputs) }
98+
99+
expect(finalInputs.connectionTimeout).toBe('')
100+
})
101+
102+
it('parses connectionTimeout when it is set', () => {
103+
const inputs = {
104+
...connection,
105+
operation: 'query',
106+
query: 'SELECT 1',
107+
connectionTimeout: '30000',
108+
}
109+
const finalInputs = { ...inputs, ...buildParams(inputs) }
110+
111+
expect(finalInputs.connectionTimeout).toBe(30000)
112+
})
113+
114+
it('does not offer a named-instance field, which cannot stay pinned to the validated IP', () => {
115+
const ids = MSSQLBlock.subBlocks.map((subBlock) => subBlock.id)
116+
117+
expect(ids).not.toContain('instanceName')
118+
})
119+
120+
/**
121+
* Duplicate subBlock ids across disjoint operations are the house pattern, but
122+
* the store seeds values by id in file order — so two entries sharing an id
123+
* must agree on their default, or the last one silently wins.
124+
*/
125+
it('keeps duplicate subBlock ids in agreement on their default value', () => {
126+
const defaultsById = new Map<string, unknown[]>()
127+
for (const subBlock of MSSQLBlock.subBlocks) {
128+
const seeded = typeof subBlock.value === 'function' ? subBlock.value({}) : undefined
129+
defaultsById.set(subBlock.id, [...(defaultsById.get(subBlock.id) ?? []), seeded])
130+
}
131+
132+
for (const [id, defaults] of defaultsById) {
133+
expect(
134+
new Set(defaults.map((value) => JSON.stringify(value))),
135+
`subBlock "${id}"`
136+
).toHaveLength(1)
137+
}
138+
})
139+
})

0 commit comments

Comments
 (0)