AdvisorSignals.integration.test.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import { screen, waitFor } from '@testing-library/react'
  2. import userEvent from '@testing-library/user-event'
  3. import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
  4. import { AdvisorSection } from '@/components/interfaces/ProjectHome/AdvisorSection'
  5. import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
  6. import { AdvisorPanel } from '@/components/ui/AdvisorPanel/AdvisorPanel'
  7. import { advisorState } from '@/state/advisor-state'
  8. import { sidebarManagerState } from '@/state/sidebar-manager-state'
  9. import { render } from '@/tests/helpers'
  10. const {
  11. mockUseProjectLintsQuery,
  12. mockUseBannedIPsQuery,
  13. mockUseSelectedProjectQuery,
  14. mockUseNotificationsV2Query,
  15. mockUseNotificationsV2UpdateMutation,
  16. mockUseTrack,
  17. } = vi.hoisted(() => ({
  18. mockUseProjectLintsQuery: vi.fn(),
  19. mockUseBannedIPsQuery: vi.fn(),
  20. mockUseSelectedProjectQuery: vi.fn(),
  21. mockUseNotificationsV2Query: vi.fn(),
  22. mockUseNotificationsV2UpdateMutation: vi.fn(),
  23. mockUseTrack: vi.fn(),
  24. }))
  25. vi.mock('common', async () => {
  26. const actual = await vi.importActual<typeof import('common')>('common')
  27. return {
  28. ...actual,
  29. useParams: () => ({ ref: 'project-ref' }),
  30. }
  31. })
  32. vi.mock('@/data/lint/lint-query', () => ({
  33. useProjectLintsQuery: mockUseProjectLintsQuery,
  34. }))
  35. vi.mock('@/data/banned-ips/banned-ips-query', () => ({
  36. useBannedIPsQuery: mockUseBannedIPsQuery,
  37. }))
  38. vi.mock('@/hooks/misc/useSelectedProject', () => ({
  39. useSelectedProjectQuery: mockUseSelectedProjectQuery,
  40. }))
  41. vi.mock('@/data/notifications/notifications-v2-query', () => ({
  42. useNotificationsV2Query: mockUseNotificationsV2Query,
  43. }))
  44. vi.mock('@/data/notifications/notifications-v2-update-mutation', () => ({
  45. useNotificationsV2UpdateMutation: mockUseNotificationsV2UpdateMutation,
  46. }))
  47. vi.mock('@/lib/constants', async (importOriginal) => ({
  48. ...(await importOriginal<typeof import('@/lib/constants')>()),
  49. IS_PLATFORM: true,
  50. }))
  51. vi.mock('@/lib/telemetry/track', () => ({
  52. useTrack: mockUseTrack,
  53. }))
  54. vi.mock('@/state/ai-assistant-state', () => ({
  55. useAiAssistantStateSnapshot: () => ({
  56. newChat: vi.fn(),
  57. }),
  58. }))
  59. vi.mock('@/components/ui/AiAssistantDropdown', () => ({
  60. AiAssistantDropdown: () => <div data-testid="advisor-assistant-dropdown" />,
  61. }))
  62. vi.mock('./AdvisorFilters', () => ({
  63. AdvisorFilters: () => <div data-testid="advisor-filters" />,
  64. }))
  65. vi.mock('./AdvisorPanelHeader', () => ({
  66. AdvisorPanelHeader: () => <div data-testid="advisor-panel-header" />,
  67. }))
  68. describe('Advisor signals integration', () => {
  69. beforeEach(() => {
  70. window.localStorage.clear()
  71. advisorState.reset()
  72. sidebarManagerState.unregisterSidebar(SIDEBAR_KEYS.ADVISOR_PANEL)
  73. sidebarManagerState.registerSidebar(SIDEBAR_KEYS.ADVISOR_PANEL, () => null)
  74. sidebarManagerState.clearActiveSidebar()
  75. mockUseTrack.mockReturnValue(vi.fn())
  76. mockUseSelectedProjectQuery.mockReturnValue({
  77. data: { ref: 'project-ref' },
  78. })
  79. mockUseProjectLintsQuery.mockImplementation((_variables, options) => {
  80. if (options?.enabled === false) {
  81. return {
  82. data: undefined,
  83. isPending: false,
  84. isError: false,
  85. }
  86. }
  87. return {
  88. data: [
  89. {
  90. cache_key: 'lint-1',
  91. name: 'unknown_lint',
  92. detail: 'Critical lint detail',
  93. level: 'ERROR',
  94. categories: ['SECURITY'],
  95. metadata: {},
  96. },
  97. ],
  98. isPending: false,
  99. isError: false,
  100. }
  101. })
  102. mockUseBannedIPsQuery.mockImplementation((_variables, options) => {
  103. if (options?.enabled === false) {
  104. return {
  105. data: undefined,
  106. isPending: false,
  107. isError: false,
  108. }
  109. }
  110. return {
  111. data: {
  112. banned_ipv4_addresses: ['203.0.113.10'],
  113. },
  114. isPending: false,
  115. isError: false,
  116. }
  117. })
  118. mockUseNotificationsV2Query.mockReturnValue({
  119. data: { pages: [[]] },
  120. isPending: false,
  121. isError: false,
  122. })
  123. mockUseNotificationsV2UpdateMutation.mockReturnValue({
  124. mutate: vi.fn(),
  125. })
  126. })
  127. afterEach(() => {
  128. advisorState.reset()
  129. sidebarManagerState.unregisterSidebar(SIDEBAR_KEYS.ADVISOR_PANEL)
  130. sidebarManagerState.clearActiveSidebar()
  131. vi.clearAllMocks()
  132. })
  133. it('renders signal items and dismisses them across the homepage and panel', async () => {
  134. render(
  135. <>
  136. <AdvisorSection />
  137. <AdvisorPanel />
  138. </>
  139. )
  140. expect(screen.getByText('Advisor found 2 issues')).toBeInTheDocument()
  141. expect(screen.getByText('Banned IP address')).toBeInTheDocument()
  142. expect(screen.getAllByText('Critical lint detail').length).toBeGreaterThan(0)
  143. expect(
  144. screen.getAllByText((_, node) =>
  145. Boolean(
  146. node?.textContent?.includes(
  147. 'The IP address 203.0.113.10 is temporarily blocked because of suspicious traffic or repeated failed password attempts.'
  148. )
  149. )
  150. ).length
  151. ).toBeGreaterThan(0)
  152. await userEvent.click(screen.getByText('Banned IP address'))
  153. expect(screen.getByText('Entity')).toBeInTheDocument()
  154. expect(screen.getByText('Issue')).toBeInTheDocument()
  155. expect(screen.getByText('Resolve')).toBeInTheDocument()
  156. expect(screen.getAllByTestId('advisor-assistant-dropdown').length).toBeGreaterThan(0)
  157. expect(
  158. screen.getAllByText((_, node) =>
  159. Boolean(
  160. node?.textContent?.includes(
  161. 'The IP address 203.0.113.10 is temporarily blocked because of suspicious traffic or repeated failed password attempts.'
  162. )
  163. )
  164. ).length
  165. ).toBeGreaterThan(0)
  166. expect(screen.getByRole('link', { name: 'Learn more' })).toHaveAttribute(
  167. 'href',
  168. 'https://supabase.com/docs/reference/cli/supabase-network-bans'
  169. )
  170. expect(screen.getAllByText('Banned IP address').length).toBeGreaterThan(0)
  171. await userEvent.click(screen.getByRole('button', { name: 'Dismiss' }))
  172. await waitFor(() => {
  173. expect(screen.queryByText('Banned IP address')).not.toBeInTheDocument()
  174. })
  175. expect(screen.getAllByText('Critical lint detail').length).toBeGreaterThan(0)
  176. })
  177. it('shows an overflow affordance when the homepage cap hides additional issues', () => {
  178. mockUseProjectLintsQuery.mockImplementation((_variables, options) => {
  179. if (options?.enabled === false) {
  180. return {
  181. data: undefined,
  182. isPending: false,
  183. isError: false,
  184. }
  185. }
  186. return {
  187. data: [
  188. {
  189. cache_key: 'lint-1',
  190. name: 'unknown_lint',
  191. detail: 'Critical lint detail 1',
  192. level: 'ERROR',
  193. categories: ['SECURITY'],
  194. metadata: {},
  195. },
  196. {
  197. cache_key: 'lint-2',
  198. name: 'unknown_lint',
  199. detail: 'Critical lint detail 2',
  200. level: 'ERROR',
  201. categories: ['SECURITY'],
  202. metadata: {},
  203. },
  204. {
  205. cache_key: 'lint-3',
  206. name: 'unknown_lint',
  207. detail: 'Critical lint detail 3',
  208. level: 'ERROR',
  209. categories: ['SECURITY'],
  210. metadata: {},
  211. },
  212. {
  213. cache_key: 'lint-4',
  214. name: 'unknown_lint',
  215. detail: 'Critical lint detail 4',
  216. level: 'ERROR',
  217. categories: ['SECURITY'],
  218. metadata: {},
  219. },
  220. ],
  221. isPending: false,
  222. isError: false,
  223. }
  224. })
  225. render(<AdvisorSection />)
  226. expect(screen.getByText('Advisor found 5 issues')).toBeInTheDocument()
  227. expect(screen.getByRole('button', { name: 'View 1 more issue in Advisor' })).toBeInTheDocument()
  228. expect(screen.queryByText('Banned IP address')).not.toBeInTheDocument()
  229. })
  230. it('does not block the homepage while signal items are pending', () => {
  231. mockUseProjectLintsQuery.mockReturnValue({
  232. data: [],
  233. isPending: false,
  234. isError: false,
  235. })
  236. mockUseBannedIPsQuery.mockReturnValue({
  237. data: undefined,
  238. isPending: true,
  239. isError: false,
  240. })
  241. render(<AdvisorSection />)
  242. // Lints have loaded — homepage renders without being blocked by signal loading
  243. expect(screen.getByText('Advisor found no issues')).toBeInTheDocument()
  244. })
  245. })