Invitations: migrate send to typed path param; update settings view and tests; spec reflects new OpenAPI and migration plan
This commit is contained in:
parent
3b421106bc
commit
630c6dabcf
3 changed files with 16 additions and 18 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { fetchApi } from '@/api/client'
|
import { api, fetchApi } from '@/api/client'
|
||||||
|
|
||||||
export type Household = { id: number; name: string; slug: string }
|
export type Household = { id: number; name: string; slug: string }
|
||||||
|
|
||||||
|
|
@ -40,11 +40,10 @@ export async function acceptInvitation(token: string): Promise<Household> {
|
||||||
return { id: h.id, name: h.name, slug: h.slug }
|
return { id: h.id, name: h.name, slug: h.slug }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendInvitation(email: string): Promise<void> {
|
export async function sendInvitation(householdSlug: string, email: string): Promise<void> {
|
||||||
const resp = await fetchApi('/api/v1/invitations', {
|
const res = await api.POST('/api/v1/households/{householdSlug}/invitations', {
|
||||||
method: 'POST',
|
params: { path: { householdSlug } },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: { email },
|
||||||
body: JSON.stringify({ email }),
|
|
||||||
})
|
})
|
||||||
if (!resp.ok && resp.status !== 204) throw new Error(`${resp.status} ${resp.statusText || 'HTTP error'}`)
|
if (!res.response.ok) throw new Error(`${res.response.status} ${res.response.statusText || 'HTTP error'}`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,15 +54,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { sendInvitation } from '@/api/invitations'
|
import { sendInvitation } from '@/api/invitations'
|
||||||
import { listMembers, type Member } from '@/api/households'
|
import { listMembers, type Member } from '@/api/households'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
|
||||||
const email = ref('')
|
const email = ref('')
|
||||||
const submitting = ref(false)
|
const submitting = ref(false)
|
||||||
const message = ref('')
|
const message = ref('')
|
||||||
const error = ref('')
|
const error = ref('')
|
||||||
const members = ref<Member[]>([])
|
const members = ref<Member[]>([])
|
||||||
|
const route = useRoute()
|
||||||
|
const householdSlug = computed(() => (typeof route.params.householdSlug === 'string' ? route.params.householdSlug : null))
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -77,7 +80,9 @@ async function onInvite() {
|
||||||
error.value = ''
|
error.value = ''
|
||||||
submitting.value = true
|
submitting.value = true
|
||||||
try {
|
try {
|
||||||
await sendInvitation(email.value.trim())
|
const slug = householdSlug.value
|
||||||
|
if (!slug) throw new Error('No household selected')
|
||||||
|
await sendInvitation(slug, email.value.trim())
|
||||||
message.value = 'Invitation sent.'
|
message.value = 'Invitation sent.'
|
||||||
email.value = ''
|
email.value = ''
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@ import { describe, it, expect } from 'vitest'
|
||||||
import { server, http, HttpResponse } from './test-setup'
|
import { server, http, HttpResponse } from './test-setup'
|
||||||
import { loginWithPassword } from '@/api/auth'
|
import { loginWithPassword } from '@/api/auth'
|
||||||
import { sendInvitation } from '@/api/invitations'
|
import { sendInvitation } from '@/api/invitations'
|
||||||
import { setHouseholdSlugProvider } from '@/api/client'
|
|
||||||
|
|
||||||
describe('invitations api (send invite)', () => {
|
describe('invitations api (send invite)', () => {
|
||||||
it('posts email with Authorization and X-Household-Slug', async () => {
|
it('posts email with Authorization and householdSlug path param', async () => {
|
||||||
// Simulate auth token
|
// Simulate auth token
|
||||||
server.use(
|
server.use(
|
||||||
http.post('*/api/v1/auth/login', () =>
|
http.post('*/api/v1/auth/login', () =>
|
||||||
|
|
@ -14,21 +13,16 @@ describe('invitations api (send invite)', () => {
|
||||||
)
|
)
|
||||||
await loginWithPassword('x@y', 'pw')
|
await loginWithPassword('x@y', 'pw')
|
||||||
|
|
||||||
// Provide a household slug for header injection
|
|
||||||
setHouseholdSlugProvider(() => 'the-smiths')
|
|
||||||
|
|
||||||
server.use(
|
server.use(
|
||||||
http.post('*/api/v1/invitations', async ({ request }) => {
|
http.post('*/api/v1/households/the-smiths/invitations', async ({ request, requestId, cookies, params }) => {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
expect(body).toEqual({ email: 'invite@example.com' })
|
expect(body).toEqual({ email: 'invite@example.com' })
|
||||||
const auth = request.headers.get('authorization')
|
const auth = request.headers.get('authorization')
|
||||||
expect(auth?.toLowerCase()).toBe('bearer tokabc')
|
expect(auth?.toLowerCase()).toBe('bearer tokabc')
|
||||||
const slug = request.headers.get('x-household-slug')
|
|
||||||
expect(slug).toBe('the-smiths')
|
|
||||||
return HttpResponse.json({}, { status: 204 })
|
return HttpResponse.json({}, { status: 204 })
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(sendInvitation('invite@example.com')).resolves.toBeUndefined()
|
await expect(sendInvitation('the-smiths', 'invite@example.com')).resolves.toBeUndefined()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue