• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cameri / nostream / 25601018106

09 May 2026 12:22PM UTC coverage: 33.99% (-31.1%) from 65.107%
25601018106

Pull #615

github

web-flow
Merge 1ef509ec3 into 36e5af87e
Pull Request #615: test: add unit tests for remaining app workers (#489)

788 of 3170 branches covered (24.86%)

Branch coverage included in aggregate %.

0 of 8 new or added lines in 2 files covered. (0.0%)

1822 existing lines in 87 files now uncovered.

2352 of 6068 relevant lines covered (38.76%)

13.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.96
/src/utils/transform.ts
1
import {
3✔
2
  always,
3
  applySpec,
4
  cond,
5
  equals,
6
  ifElse,
7
  is,
8
  isNil,
9
  multiply,
10
  path,
11
  pathSatisfies,
12
  pipe,
13
  prop,
14
  propSatisfies,
15
  T,
16
} from 'ramda'
17

18
import { Invoice, InvoiceStatus, InvoiceUnit } from '../@types/invoice'
3✔
19
import { User } from '../@types/user'
20

21
const BECH32_ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
3✔
22
const BECH32_ALPHABET_MAP: Record<string, number> = {}
3✔
23
for (let i = 0; i < BECH32_ALPHABET.length; i++) { BECH32_ALPHABET_MAP[BECH32_ALPHABET[i]] = i }
96✔
24

25
function bech32PolymodStep(pre: number): number {
26
  const b = pre >> 25
201✔
27
  return (((pre & 0x1ffffff) << 5) ^
201✔
28
    (-((b >> 0) & 1) & 0x3b6a57b2) ^
29
    (-((b >> 1) & 1) & 0x26508e6d) ^
30
    (-((b >> 2) & 1) & 0x1ea119fa) ^
31
    (-((b >> 3) & 1) & 0x3d4233dd) ^
32
    (-((b >> 4) & 1) & 0x2a1462b3))
33
}
34

35
function bech32PrefixChk(prefix: string): number {
36
  let chk = 1
3✔
37
  for (let i = 0; i < prefix.length; ++i) {
3✔
38
    const c = prefix.charCodeAt(i)
12✔
39
    chk = bech32PolymodStep(chk) ^ (c >> 5)
12✔
40
  }
41
  chk = bech32PolymodStep(chk)
3✔
42
  for (let i = 0; i < prefix.length; ++i) {
3✔
43
    chk = bech32PolymodStep(chk) ^ (prefix.charCodeAt(i) & 0x1f)
12✔
44
  }
45
  return chk
3✔
46
}
47

48
function bech32Convert(data: number[], inBits: number, outBits: number, pad: boolean): number[] {
49
  let value = 0, bits = 0
3✔
50
  const maxV = (1 << outBits) - 1
3✔
51
  const maxAcc = (1 << (inBits + outBits - 1)) - 1
3✔
52
  const maxInput = (1 << inBits) - 1
3✔
53
  const result: number[] = []
3✔
54
  for (const byte of data) {
3✔
55
    if (!Number.isInteger(byte) || byte < 0 || byte > maxInput) {
96!
56
      throw new Error(`Invalid value for ${inBits}-bit input: ${byte}`)
×
57
    }
58
    value = ((value << inBits) | byte) & maxAcc
96✔
59
    bits += inBits
96✔
60
    while (bits >= outBits) {
96✔
61
      bits -= outBits
153✔
62
      result.push((value >> bits) & maxV)
153✔
63
    }
64
  }
65
  if (pad) {
3!
66
    if (bits > 0) { result.push((value << (outBits - bits)) & maxV) }
3!
67
  } else if (bits >= inBits || ((value << (outBits - bits)) & maxV) !== 0) {
×
68
    throw new Error('Invalid bech32 padding')
×
69
  }
70
  return result
3✔
71
}
72

73
function bech32Decode(str: string): { prefix: string; words: number[] } {
UNCOV
74
  const lower = str.toLowerCase()
×
UNCOV
75
  const split = lower.lastIndexOf('1')
×
UNCOV
76
  if (split < 1 || split + 7 > str.length) { throw new Error(`Invalid bech32: ${str}`) }
×
UNCOV
77
  const prefix = lower.slice(0, split)
×
UNCOV
78
  const wordChars = lower.slice(split + 1)
×
UNCOV
79
  let chk = bech32PrefixChk(prefix)
×
UNCOV
80
  const words: number[] = []
×
UNCOV
81
  for (let i = 0; i < wordChars.length; ++i) {
×
UNCOV
82
    const v = BECH32_ALPHABET_MAP[wordChars[i]]
×
UNCOV
83
    if (v === undefined) { throw new Error(`Unknown bech32 character: ${wordChars[i]}`) }
×
84
    chk = bech32PolymodStep(chk) ^ v
×
85
    if (i + 6 < wordChars.length) { words.push(v) }
×
86
  }
87
  if (chk !== 1) { throw new Error('Invalid bech32 checksum') }
×
88
  return { prefix, words }
×
89
}
90

91
function bech32Encode(prefix: string, words: number[]): string {
92
  prefix = prefix.toLowerCase()
3✔
93
  let chk = bech32PrefixChk(prefix)
3✔
94
  let result = prefix + '1'
3✔
95
  for (const w of words) {
3✔
96
    chk = bech32PolymodStep(chk) ^ w
156✔
97
    result += BECH32_ALPHABET[w]
156✔
98
  }
99
  for (let i = 0; i < 6; ++i) { chk = bech32PolymodStep(chk) }
18✔
100
  chk ^= 1
3✔
101
  for (let i = 0; i < 6; ++i) { result += BECH32_ALPHABET[(chk >> ((5 - i) * 5)) & 0x1f] }
18✔
102
  return result
3✔
103
}
104

105
export const toJSON = (input: any) => JSON.stringify(input)
65✔
106

107
export const toBuffer = (input: any) => Buffer.from(input, 'hex')
647✔
108

109
export const fromBuffer = (input: Buffer) => input.toString('hex')
212✔
110

111
export const toBigInt = (input: string | number): bigint => BigInt(input)
3✔
112

113
export const fromBigInt = (input: bigint) => input.toString()
3✔
114

115
const addTime = (ms: number) => (input: Date) => new Date(input.getTime() + ms)
3✔
116

117
export const fromDBInvoice = applySpec<Invoice>({
3✔
118
  id: prop('id') as () => string,
119
  pubkey: pipe(prop('pubkey') as () => Buffer, fromBuffer),
120
  bolt11: prop('bolt11'),
121
  amountRequested: pipe(prop('amount_requested') as () => string, toBigInt),
122
  amountPaid: ifElse(
123
    propSatisfies(isNil, 'amount_paid'),
124
    always(undefined),
125
    pipe(prop('amount_paid') as () => string, toBigInt),
126
  ),
127
  unit: prop('unit'),
128
  status: prop('status'),
129
  description: prop('description'),
130
  confirmedAt: prop('confirmed_at'),
131
  expiresAt: prop('expires_at'),
132
  updatedAt: prop('updated_at'),
133
  createdAt: prop('created_at'),
134
  verifyURL: prop('verify_url'),
135
})
136

137
export const fromDBUser = applySpec<User>({
3✔
138
  pubkey: pipe(prop('pubkey') as () => Buffer, fromBuffer),
139
  isAdmitted: prop('is_admitted'),
140
  isVanished: prop('is_vanished'),
141
  balance: prop('balance'),
142
  createdAt: prop('created_at'),
143
  updatedAt: prop('updated_at'),
144
})
145

146
export const fromBech32 = (input: string) => {
3✔
UNCOV
147
  const normalizedInput = input.toLowerCase()
×
148

UNCOV
149
  if (input !== normalizedInput && input !== input.toUpperCase()) {
×
150
    throw new Error('Bech32 mixed-case input is invalid')
×
151
  }
152

UNCOV
153
  const { prefix, words } = bech32Decode(input)
×
154
  if (!normalizedInput.startsWith(prefix)) {
×
155
    throw new Error(`Bech32 invalid prefix: ${prefix}`)
×
156
  }
157

158
  return Buffer.from(
×
159
    bech32Convert(words, 5, 8, false).slice(0, 32)
160
  ).toString('hex')
161
}
162

163
export const toBech32 = (prefix: string) => (input: string): string => {
3✔
164
  return bech32Encode(prefix, bech32Convert(Array.from(Buffer.from(input, 'hex')), 8, 5, true))
3✔
165
}
166

167
export const toDate = (input: string | number) => new Date(input)
3✔
168

169
export const fromZebedeeInvoice = applySpec<Invoice>({
3✔
170
  id: prop('id'),
171
  pubkey: prop('internalId'),
172
  bolt11: path(['invoice', 'request']),
173
  amountRequested: pipe(prop('amount') as () => string, toBigInt),
174
  description: prop('description'),
175
  unit: prop('unit'),
176
  status: prop('status'),
177
  expiresAt: ifElse(propSatisfies(is(String), 'expiresAt'), pipe(prop('expiresAt'), toDate), always(null)),
178
  confirmedAt: ifElse(propSatisfies(is(String), 'confirmedAt'), pipe(prop('confirmedAt'), toDate), always(null)),
179
  createdAt: ifElse(propSatisfies(is(String), 'createdAt'), pipe(prop('createdAt'), toDate), always(null)),
180
  rawResponse: toJSON,
181
})
182

183
export const fromNodelessInvoice = applySpec<Invoice>({
3✔
184
  id: prop('id'),
185
  pubkey: path(['metadata', 'requestId']),
186
  bolt11: prop('lightningInvoice'),
187
  amountRequested: pipe(prop('satsAmount') as () => number, toBigInt),
188
  description: path(['metadata', 'description']),
189
  unit: path(['metadata', 'unit']),
190
  status: pipe(
191
    prop('status'),
192
    cond([
193
      [equals('new'), always(InvoiceStatus.PENDING)],
194
      [equals('pending_confirmation'), always(InvoiceStatus.PENDING)],
195
      [equals('underpaid'), always(InvoiceStatus.PENDING)],
196
      [equals('in_flight'), always(InvoiceStatus.PENDING)],
197
      [equals('paid'), always(InvoiceStatus.COMPLETED)],
198
      [equals('overpaid'), always(InvoiceStatus.COMPLETED)],
199
      [equals('expired'), always(InvoiceStatus.EXPIRED)],
200
    ]),
201
  ),
202
  expiresAt: ifElse(
203
    propSatisfies(is(String), 'expiresAt'),
204
    pipe(prop('expiresAt'), toDate),
205
    ifElse(propSatisfies(is(String), 'createdAt'), pipe(prop('createdAt'), toDate, addTime(15 * 60000)), always(null)),
206
  ),
207
  confirmedAt: cond([
208
    [propSatisfies(is(String), 'paidAt'), pipe(prop('paidAt'), toDate)],
209
    [T, always(null)],
210
  ]),
211
  createdAt: ifElse(propSatisfies(is(String), 'createdAt'), pipe(prop('createdAt'), toDate), always(null)),
212
  // rawResponse: toJSON,
213
})
214

215
export const fromOpenNodeInvoice = applySpec<Invoice>({
3✔
216
  id: prop('id'),
217
  pubkey: prop('order_id'),
218
  bolt11: ifElse(
219
    pathSatisfies(is(String), ['lightning_invoice', 'payreq']),
220
    path(['lightning_invoice', 'payreq']),
221
    path(['lightning', 'payreq']),
222
  ),
223
  amountRequested: pipe(
224
    ifElse(propSatisfies(is(Number), 'amount'), prop('amount'), prop('price')) as () => number,
225
    toBigInt,
226
  ),
227
  description: prop('description'),
228
  unit: always(InvoiceUnit.SATS),
229
  status: pipe(
230
    prop('status'),
231
    cond([
232
      [equals('expired'), always(InvoiceStatus.EXPIRED)],
233
      [equals('refunded'), always(InvoiceStatus.EXPIRED)],
234
      [equals('unpaid'), always(InvoiceStatus.PENDING)],
235
      [equals('processing'), always(InvoiceStatus.PENDING)],
236
      [equals('underpaid'), always(InvoiceStatus.PENDING)],
237
      [equals('paid'), always(InvoiceStatus.COMPLETED)],
238
    ]),
239
  ),
240
  expiresAt: pipe(
241
    cond([
242
      [pathSatisfies(is(String), ['lightning', 'expires_at']), path(['lightning', 'expires_at'])],
243
      [
244
        pathSatisfies(is(Number), ['lightning_invoice', 'expires_at']),
245
        pipe(path(['lightning_invoice', 'expires_at']), multiply(1000)),
246
      ],
247
    ]),
248
    toDate,
249
  ),
250
  confirmedAt: cond([
251
    [propSatisfies(equals('paid'), 'status'), () => new Date()],
×
252
    [T, always(null)],
253
  ]),
254
  createdAt: pipe(
255
    ifElse(propSatisfies(is(Number), 'created_at'), pipe(prop('created_at'), multiply(1000)), prop('created_at')),
256
    toDate,
257
  ),
258
  rawResponse: toJSON,
259
})
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc