-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.test.ts
208 lines (203 loc) · 7.46 KB
/
index.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import {afterEach, beforeEach, describe, expect, test, vi} from 'vitest'
import createFetchMock from 'vitest-fetch-mock'
import {BskyAgent} from '@atproto/api'
import {login} from './bluesky.js'
import {
handlePayload,
isSongfishPayload,
type LambdaEvent,
type SongfishWebhookPayload,
} from './index.js'
import testFixture from './test-fixture.json'
vi.mock('./bluesky.js') // use e.g.: vi.mocked(login).mockResolvedValue({})
const fetchMocker = createFetchMock(vi)
fetchMocker.enableMocks()
function mockJson(urlMatcher, jsonPayload) {
fetchMocker.mockIf(urlMatcher, () => ({
body: JSON.stringify(jsonPayload),
contentType: 'application/json',
}))
}
describe('isSongfishPayload', () => {
test('is a function', () => {
expect(typeof isSongfishPayload).toBe('function')
})
describe('with non-object payload', () => {
test('is false', () => {
expect(isSongfishPayload(undefined)).toBe(false)
expect(isSongfishPayload(9)).toBe(false)
expect(isSongfishPayload('foo')).toBe(false)
})
})
describe('with object payload', () => {
describe('when object does not have `body`', () => {
test('is false', () => {
expect(isSongfishPayload({foo:'bar'})).toBe(false)
})
})
describe('when object does have `body`', () => {
describe('but it is not stringified JSON', () => {
test('is false', () => {
expect(isSongfishPayload({body:[]})).toBe(false)
})
})
describe('set to stringified JSON', () => {
describe('when stringified JSON is _not_ an object with a `show_id` key', () => {
test('is false', () => {
expect(isSongfishPayload({body:'[1, 2, 3]'})).toBe(false)
expect(isSongfishPayload({body:'{}'})).toBe(false)
})
})
describe('when stringified JSON _is_ an object with a `show_id` key', () => {
test('is true', () => {
expect(isSongfishPayload({body:'{"show_id": 999}'})).toBe(true)
})
})
})
})
})
})
describe('handlePayload', () => {
test('is a function', () => {
expect(typeof handlePayload).toBe('function')
})
describe('with malformed payload', () => {
test('throws with the error message from parsing the payload', async () => {
// @ts-expect-error test passing invalid string argument
await expect(() => handlePayload([])).rejects.toThrow('not valid JSON')
})
})
describe('with valid payload', () => {
let payload
beforeEach(() => {
const data = {
show_id: 123
}
payload = {
body: JSON.stringify(data)
}
})
describe('with invalid login', () => {
beforeEach(() => {
vi.mocked(login).mockRejectedValue('mocked login failure')
})
test('throws with the error message from logging in', async () => {
await expect(() => handlePayload(payload)).rejects.toThrow('mocked login failure')
})
})
describe('with valid login and prior post does not match latest song title', () => {
const mockedLoginReturnValue = {
getAuthorFeed: vi.fn().mockReturnValueOnce({data: {feed: [{post: {record: {text: 'Prior Post'}}}] }}),
}
beforeEach(() => {
vi.mocked(login).mockResolvedValue(mockedLoginReturnValue as unknown as BskyAgent)
})
afterEach(() => {
vi.mocked(login).mockReset()
})
describe(`with malformed Latest.json`, () => {
beforeEach(() => {
fetchMocker.mockIf(/\bkglw\.net\b.+\blatest\.json$/, () => 'this mocked Songfish response is malformed JSON')
})
test('returns a helpful message', async () => {
await expect(handlePayload(payload)).rejects.toThrow('not valid JSON')
})
})
describe(`when payload's show_id does _not_ match fetched JSON's data[-1].show_id`, () => {
let mockedPost
beforeEach(() => {
mockedPost = vi.fn()
vi.mocked(login).mockResolvedValue({
...mockedLoginReturnValue,
post: mockedPost,
} as unknown as BskyAgent)
mockJson(/\bkglw\.net\b.+\blatest\.json$/, {data: [
{show_id: 666, songname: 'Most Recent Song Name'},
]})
})
test('returns a helpful message', async () => {
await expect(handlePayload(payload)).resolves.toBe(
'payload show_id does not match latest show'
)
expect(mockedPost).not.toHaveBeenCalled()
})
})
describe(`when payload's show_id matches fetched JSON's data[-1].show_id`, () => {
let mockedPost
beforeEach(() => {
mockedPost = vi.fn().mockReturnValueOnce({mocked: true})
vi.mocked(login).mockResolvedValue({
...mockedLoginReturnValue,
post: mockedPost,
} as unknown as BskyAgent)
mockJson(/\bkglw\.net\b.+\blatest\.json$/, {data: [
{show_id: 789, songname: 'A Different Show and Song'},
{show_id: 456, songname: 'Yet Another Different Show and Song'},
{show_id: 123, songname: 'Most Recent Song Name'},
]})
})
test('posts the song title', async () => {
await handlePayload(payload)
expect(mockedPost).toHaveBeenCalledWith({text: 'Most Recent Song Name'})
})
})
})
describe('with valid login and prior post _does_ match latest song title', () => {
const mockedLoginReturnValue = {
getAuthorFeed: vi.fn().mockReturnValueOnce({data: {feed: [{post: {record: {text: 'Song Title'}}}] }}),
}
beforeEach(() => {
vi.mocked(login).mockResolvedValue(mockedLoginReturnValue as unknown as BskyAgent)
})
afterEach(() => {
vi.mocked(login).mockReset()
})
describe(`when payload's show_id matches fetched JSON's data[-1].show_id`, () => {
let mockedPost
beforeEach(() => {
mockedPost = vi.fn().mockReturnValueOnce({mocked: true})
vi.mocked(login).mockResolvedValue({
...mockedLoginReturnValue,
post: mockedPost,
} as unknown as BskyAgent)
mockJson(/\bkglw\.net\b.+\blatest\.json$/, {data: [
{show_id: 123, songname: 'Song Title'},
]})
})
test('does _not_ post the song title', async () => {
await handlePayload(payload)
expect(mockedPost).not.toHaveBeenCalled()
})
})
})
})
describe('with fixture payload matching latest show_id', () => {
const testWithFixture = test.extend({
event: async ({}, use) => {
await use(testFixture.event)
}
})
const mockedLoginReturnValue = {
getAuthorFeed: vi.fn().mockReturnValueOnce({data: {feed: [{post: {record: {text: 'Prior Post'}}}] }}),
}
let mockedPost
beforeEach(() => {
mockedPost = vi.fn().mockReturnValueOnce({mocked: true})
vi.mocked(login).mockResolvedValue({
...mockedLoginReturnValue,
post: mockedPost,
} as unknown as BskyAgent)
mockJson(/\bkglw\.net\b.+\blatest\.json$/, {data: [
// the id 1699404057 is defined in the fixture file
{show_id: 1699404057, songname: 'Name of Song From Show #1699404057'},
]})
})
afterEach(() => {
vi.mocked(login).mockReset()
})
testWithFixture('does not throw', async ({event}:{event:LambdaEvent<SongfishWebhookPayload>}) => {
await expect(handlePayload(event)).resolves.not.toThrow()
expect(mockedPost).toHaveBeenCalledWith({text: 'Name of Song From Show #1699404057'})
})
})
})