WithStatements.utils.test.ts161 lines · main
| 1 | import { safeSql } from '@supabase/pg-meta' |
| 2 | import { describe, expect, it, vi } from 'vitest' |
| 3 | |
| 4 | import { transformStatementDataToRows } from './WithStatements.utils' |
| 5 | |
| 6 | vi.mock('../IndexAdvisor/index-advisor.utils', () => ({ |
| 7 | filterProtectedSchemaIndexAdvisorResult: vi.fn((result) => { |
| 8 | if (result?._mock_filter_null) return null |
| 9 | return result |
| 10 | }), |
| 11 | queryInvolvesProtectedSchemas: vi.fn((query: string) => { |
| 12 | return query?.toLowerCase().includes('auth.') |
| 13 | }), |
| 14 | })) |
| 15 | |
| 16 | const makeRow = (overrides: Record<string, any> = {}) => ({ |
| 17 | query: safeSql`SELECT 1`, |
| 18 | rolname: 'postgres', |
| 19 | calls: 10, |
| 20 | mean_time: 5.0, |
| 21 | min_time: 1.0, |
| 22 | max_time: 20.0, |
| 23 | total_time: 50.0, |
| 24 | prop_total_time: 0, |
| 25 | rows_read: 100, |
| 26 | cache_hit_rate: 0.95, |
| 27 | index_advisor_result: null, |
| 28 | ...overrides, |
| 29 | }) |
| 30 | |
| 31 | describe('transformStatementDataToRows', () => { |
| 32 | it('returns empty array for null or empty input', () => { |
| 33 | expect(transformStatementDataToRows(null as any)).toEqual([]) |
| 34 | expect(transformStatementDataToRows([])).toEqual([]) |
| 35 | }) |
| 36 | |
| 37 | it('transforms basic rows correctly', () => { |
| 38 | const data = [makeRow()] |
| 39 | const result = transformStatementDataToRows(data) |
| 40 | |
| 41 | expect(result).toHaveLength(1) |
| 42 | expect(result[0]).toMatchObject({ |
| 43 | query: 'SELECT 1', |
| 44 | rolname: 'postgres', |
| 45 | calls: 10, |
| 46 | mean_time: 5.0, |
| 47 | min_time: 1.0, |
| 48 | max_time: 20.0, |
| 49 | total_time: 50.0, |
| 50 | rows_read: 100, |
| 51 | cache_hit_rate: 0.95, |
| 52 | }) |
| 53 | }) |
| 54 | |
| 55 | it('preserves zero-valued numeric fields as 0', () => { |
| 56 | const data = [ |
| 57 | makeRow({ |
| 58 | calls: 0, |
| 59 | mean_time: 0, |
| 60 | min_time: 0, |
| 61 | max_time: 0, |
| 62 | total_time: 0, |
| 63 | rows_read: 0, |
| 64 | cache_hit_rate: 0, |
| 65 | }), |
| 66 | ] |
| 67 | const result = transformStatementDataToRows(data) |
| 68 | |
| 69 | expect(result).toHaveLength(1) |
| 70 | expect(result[0].calls).toBe(0) |
| 71 | expect(result[0].mean_time).toBe(0) |
| 72 | expect(result[0].min_time).toBe(0) |
| 73 | expect(result[0].max_time).toBe(0) |
| 74 | expect(result[0].total_time).toBe(0) |
| 75 | expect(result[0].rows_read).toBe(0) |
| 76 | expect(result[0].cache_hit_rate).toBe(0) |
| 77 | }) |
| 78 | |
| 79 | it('sets rolname to undefined when missing', () => { |
| 80 | const data = [makeRow({ rolname: undefined })] |
| 81 | const result = transformStatementDataToRows(data) |
| 82 | expect(result[0].rolname).toBeUndefined() |
| 83 | }) |
| 84 | |
| 85 | it('calculates prop_total_time as percentage of total time', () => { |
| 86 | const data = [ |
| 87 | makeRow({ query: safeSql`Q1`, total_time: 75 }), |
| 88 | makeRow({ query: safeSql`Q2`, total_time: 25 }), |
| 89 | ] |
| 90 | const result = transformStatementDataToRows(data) |
| 91 | |
| 92 | expect(result[0].prop_total_time).toBe(75) |
| 93 | expect(result[1].prop_total_time).toBe(25) |
| 94 | }) |
| 95 | |
| 96 | it('handles prop_total_time when total is zero', () => { |
| 97 | const data = [makeRow({ total_time: 0 })] |
| 98 | const result = transformStatementDataToRows(data) |
| 99 | expect(result[0].prop_total_time).toBe(0) |
| 100 | }) |
| 101 | |
| 102 | it('applies index_advisor_result filtering', () => { |
| 103 | const data = [ |
| 104 | makeRow({ |
| 105 | index_advisor_result: { index_statements: ['CREATE INDEX ON public.users (id)'] }, |
| 106 | }), |
| 107 | ] |
| 108 | const result = transformStatementDataToRows(data) |
| 109 | |
| 110 | expect(result[0].index_advisor_result).toEqual({ |
| 111 | index_statements: ['CREATE INDEX ON public.users (id)'], |
| 112 | }) |
| 113 | }) |
| 114 | |
| 115 | it('sets index_advisor_result to null when source is null', () => { |
| 116 | const data = [makeRow({ index_advisor_result: null })] |
| 117 | const result = transformStatementDataToRows(data) |
| 118 | expect(result[0].index_advisor_result).toBeNull() |
| 119 | }) |
| 120 | |
| 121 | describe('filterIndexAdvisor mode', () => { |
| 122 | it('keeps rows for non-protected schema queries', () => { |
| 123 | const data = [makeRow({ query: safeSql`SELECT * FROM public.users` })] |
| 124 | const result = transformStatementDataToRows(data, true) |
| 125 | expect(result).toHaveLength(1) |
| 126 | }) |
| 127 | |
| 128 | it('keeps protected-schema rows that have valid recommendations', () => { |
| 129 | const data = [ |
| 130 | makeRow({ |
| 131 | query: safeSql`SELECT * FROM auth.users`, |
| 132 | index_advisor_result: { index_statements: ['CREATE INDEX ON auth.users (id)'] }, |
| 133 | }), |
| 134 | ] |
| 135 | const result = transformStatementDataToRows(data, true) |
| 136 | expect(result).toHaveLength(1) |
| 137 | }) |
| 138 | |
| 139 | it('filters out protected-schema rows with no valid recommendations', () => { |
| 140 | const data = [ |
| 141 | makeRow({ |
| 142 | query: safeSql`SELECT * FROM auth.users`, |
| 143 | index_advisor_result: { _mock_filter_null: true }, |
| 144 | }), |
| 145 | ] |
| 146 | const result = transformStatementDataToRows(data, true) |
| 147 | expect(result).toHaveLength(0) |
| 148 | }) |
| 149 | |
| 150 | it('does not filter protected-schema rows when filterIndexAdvisor is false', () => { |
| 151 | const data = [ |
| 152 | makeRow({ |
| 153 | query: safeSql`SELECT * FROM auth.users`, |
| 154 | index_advisor_result: { _mock_filter_null: true }, |
| 155 | }), |
| 156 | ] |
| 157 | const result = transformStatementDataToRows(data, false) |
| 158 | expect(result).toHaveLength(1) |
| 159 | }) |
| 160 | }) |
| 161 | }) |