Files
zeekay 40d120dbf7 refactor(api): say /v1/ in the Python callers
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.
2026-08-08 18:38:44 -07:00

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 })
})
})
})