mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
Every first-party Python caller and test now names the mount the API actually answers on. Mechanical: URL literals only, and only where the /api/ was ours -- a preceding word character means it was a module path like insights/api/ or a third-party host like slack.com/api/, and those are untouched. Left saying /api/, each for a reason rather than for safety: - products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints - services/llm-gateway deploys separately and is under a code freeze, so moving its two calls would break it during the skew - the mcp_store and Slack/Teams redirect_uris are registered with third parties and must byte-match what was registered - the Wizard and Raycast client-metadata paths are OAuth client_ids - /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the ingress routes to the Rust fleets, held by released SDK versions Test URLconfs moved with the requests that call them, and the Datastore user-selection fixtures now carry the leading slash that request.path has.
242 lines
11 KiB
TypeScript
242 lines
11 KiB
TypeScript
import { MOCK_DEFAULT_ORGANIZATION, MOCK_DEFAULT_USER, MOCK_USER_UUID } from 'lib/api.mock'
|
|
|
|
import { router } from 'kea-router'
|
|
import { expectLogic } from 'kea-test-utils'
|
|
|
|
import { FEATURE_FLAGS } from 'lib/constants'
|
|
import { featureFlagLogic } from 'lib/logic/featureFlagLogic'
|
|
import { organizationLogic } from 'scenes/organizationLogic'
|
|
import { sceneLogic } from 'scenes/sceneLogic'
|
|
import { Scene } from 'scenes/sceneTypes'
|
|
import { urls } from 'scenes/urls'
|
|
import { userLogic } from 'scenes/userLogic'
|
|
|
|
import { useMocks } from '~/mocks/jest'
|
|
import { initKeaTests } from '~/test/init'
|
|
|
|
import { TargetTypeEnumApi } from 'products/subscriptions/frontend/generated/api.schemas'
|
|
|
|
import { subscriptionsSceneLogic, SubscriptionsTab } from './subscriptionsSceneLogic'
|
|
|
|
const EMPTY_SUBSCRIPTIONS = { count: 0, results: [] as unknown[] }
|
|
|
|
const blankScene = (): any => ({ scene: { component: () => null, logic: null } })
|
|
const scenes: any = { [Scene.Subscriptions]: blankScene }
|
|
|
|
function subscriptionListParamsFromUrl(url: string): URLSearchParams {
|
|
return new URL(url).searchParams
|
|
}
|
|
|
|
describe('subscriptionsSceneLogic', () => {
|
|
let logic: ReturnType<typeof subscriptionsSceneLogic.build>
|
|
let subscriptionRequestUrls: string[]
|
|
|
|
beforeEach(() => {
|
|
subscriptionRequestUrls = []
|
|
useMocks({
|
|
get: {
|
|
'/v1/projects/:team_id/subscriptions/': ({ request }) => {
|
|
subscriptionRequestUrls.push(request.url)
|
|
return [200, EMPTY_SUBSCRIPTIONS]
|
|
},
|
|
},
|
|
})
|
|
initKeaTests()
|
|
sceneLogic({ scenes }).mount()
|
|
userLogic.mount()
|
|
userLogic.actions.loadUserSuccess(MOCK_DEFAULT_USER)
|
|
router.actions.push(urls.subscriptions())
|
|
logic = subscriptionsSceneLogic()
|
|
logic.mount()
|
|
})
|
|
|
|
describe('list requests', () => {
|
|
it('loads with default ordering -created_at', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
const params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('ordering')).toBe('-created_at')
|
|
expect(params.get('limit')).toBe('20')
|
|
expect(params.get('offset')).toBe('0')
|
|
})
|
|
|
|
it('sends resource_type=dashboard on Dashboard tab', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setCurrentTab(SubscriptionsTab.Dashboard)
|
|
}).toDispatchActions(['setCurrentTab', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
const params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('resource_type')).toBe('dashboard')
|
|
})
|
|
|
|
it('sends resource_type=insight on Insight tab', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setCurrentTab(SubscriptionsTab.Insight)
|
|
}).toDispatchActions(['setCurrentTab', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
const params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('resource_type')).toBe('insight')
|
|
})
|
|
|
|
it('sends resource_type=ai_prompt on AI reports tab', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
organizationLogic.actions.loadCurrentOrganizationSuccess({
|
|
...MOCK_DEFAULT_ORGANIZATION,
|
|
is_ai_data_processing_approved: true,
|
|
})
|
|
featureFlagLogic.actions.setFeatureFlags([], { [FEATURE_FLAGS.SUBSCRIPTION_AI_PROMPT]: true })
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setCurrentTab(SubscriptionsTab.AI)
|
|
}).toDispatchActions(['setCurrentTab', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
const params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('resource_type')).toBe('ai_prompt')
|
|
})
|
|
|
|
it('ignores an AI reports URL when AI subscriptions are unavailable', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
router.actions.push(`${urls.subscriptions()}?tab=ai_prompt`)
|
|
})
|
|
.toFinishAllListeners()
|
|
.toMatchValues({ currentTab: SubscriptionsTab.All })
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
expect(subscriptionListParamsFromUrl(subscriptionRequestUrls[0]).has('resource_type')).toBe(false)
|
|
})
|
|
|
|
it('sends created_by for Mine tab', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setCurrentTab(SubscriptionsTab.Mine)
|
|
}).toDispatchActions(['setCurrentTab', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
const params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('created_by')).toBe(MOCK_USER_UUID)
|
|
})
|
|
|
|
it('maps next delivery sorting to ordering query params', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setSubscriptionsSorting({ columnKey: 'next_delivery_date', order: 1 })
|
|
}).toDispatchActions(['setSubscriptionsSorting', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
let params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('ordering')).toBe('next_delivery_date')
|
|
|
|
subscriptionRequestUrls.length = 0
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setSubscriptionsSorting({ columnKey: 'next_delivery_date', order: -1 })
|
|
}).toDispatchActions(['setSubscriptionsSorting', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('ordering')).toBe('-next_delivery_date')
|
|
})
|
|
|
|
it('maps name, created by, and created sorting to ordering query params', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setSubscriptionsSorting({ columnKey: 'name', order: 1 })
|
|
}).toDispatchActions(['setSubscriptionsSorting', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
expect(subscriptionListParamsFromUrl(subscriptionRequestUrls[0]).get('ordering')).toBe('title')
|
|
|
|
subscriptionRequestUrls.length = 0
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setSubscriptionsSorting({ columnKey: 'created_by', order: -1 })
|
|
}).toDispatchActions(['setSubscriptionsSorting', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
expect(subscriptionListParamsFromUrl(subscriptionRequestUrls[0]).get('ordering')).toBe('-created_by__email')
|
|
|
|
subscriptionRequestUrls.length = 0
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setSubscriptionsSorting({ columnKey: 'created_at', order: 1 })
|
|
}).toDispatchActions(['setSubscriptionsSorting', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
expect(subscriptionListParamsFromUrl(subscriptionRequestUrls[0]).get('ordering')).toBe('created_at')
|
|
})
|
|
|
|
it('reloads after deleteSubscriptionSuccess', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.deleteSubscriptionSuccess()
|
|
}).toDispatchActions(['deleteSubscriptionSuccess', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
})
|
|
|
|
it('passes search to the list API after setSearch (debounced listener)', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setSearch('weekly')
|
|
})
|
|
.toFinishAllListeners()
|
|
.toDispatchActions(['setSearch', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
.toMatchValues({ search: 'weekly' })
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
const params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('search')).toBe('weekly')
|
|
})
|
|
|
|
it('sends target_type when channel filter is set', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
subscriptionRequestUrls.length = 0
|
|
|
|
await expectLogic(logic, () => {
|
|
logic.actions.setTargetTypeFilter(TargetTypeEnumApi.Slack)
|
|
}).toDispatchActions(['setTargetTypeFilter', 'loadSubscriptions', 'loadSubscriptionsSuccess'])
|
|
|
|
expect(subscriptionRequestUrls).toHaveLength(1)
|
|
const params = subscriptionListParamsFromUrl(subscriptionRequestUrls[0])
|
|
expect(params.get('target_type')).toBe('slack')
|
|
})
|
|
})
|
|
|
|
describe('modal routing', () => {
|
|
it('opens the modal on /subscriptions/new and closes when navigating back to the list', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
|
|
await expectLogic(logic, () => {
|
|
router.actions.push(urls.subscriptionNew())
|
|
}).toMatchValues({ subscriptionModalId: 'new' })
|
|
|
|
await expectLogic(logic, () => {
|
|
router.actions.push(urls.subscriptions())
|
|
}).toMatchValues({ subscriptionModalId: null })
|
|
})
|
|
|
|
it('opens the modal with the subscription id on /subscriptions/:id/edit', async () => {
|
|
await expectLogic(logic).toDispatchActions(['loadSubscriptionsSuccess'])
|
|
|
|
await expectLogic(logic, () => {
|
|
router.actions.push(urls.subscriptionEdit('42'))
|
|
}).toMatchValues({ subscriptionModalId: 42 })
|
|
})
|
|
})
|
|
})
|