• 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

15.15
/src/controllers/callbacks/nodeless-callback-controller.ts
1
import { timingSafeEqual } from 'crypto'
1✔
2

3
import { always, applySpec, ifElse, is, path, prop, propEq, propSatisfies } from 'ramda'
1✔
4
import { Request, Response } from 'express'
5

6
import { Invoice, InvoiceStatus } from '../../@types/invoice'
1✔
7
import { createLogger } from '../../factories/logger-factory'
1✔
8
import { fromNodelessInvoice } from '../../utils/transform'
1✔
9
import { hmacSha256 } from '../../utils/secret'
1✔
10
import { IController } from '../../@types/controllers'
11
import { IPaymentsService } from '../../@types/services'
12
import { nodelessCallbackBodySchema, nodelessSignatureSchema } from '../../schemas/nodeless-callback-schema'
1✔
13
import { validateSchema } from '../../utils/validation'
1✔
14

15
const logger = createLogger('nodeless-callback-controller')
1✔
16

17
export class NodelessCallbackController implements IController {
1✔
UNCOV
18
  public constructor(private readonly paymentsService: IPaymentsService) {}
×
19

20
  public async handleRequest(request: Request, response: Response) {
UNCOV
21
    logger('callback request headers: %o', request.headers)
×
UNCOV
22
    logger('callback request body: %O', request.body)
×
23

UNCOV
24
    const bodyValidation = validateSchema(nodelessCallbackBodySchema)(request.body)
×
UNCOV
25
    if (bodyValidation.error) {
×
UNCOV
26
      logger('nodeless callback request rejected: invalid body %o', bodyValidation.error)
×
UNCOV
27
      response
×
28
        .status(400)
29
        .setHeader('content-type', 'application/json; charset=utf8')
30
        .send('{"status":"error","message":"Malformed body"}')
UNCOV
31
      return
×
32
    }
33

UNCOV
34
    const webhookSecret = process.env.NODELESS_WEBHOOK_SECRET
×
UNCOV
35
    if (!webhookSecret) {
×
UNCOV
36
      logger.error('NODELESS_WEBHOOK_SECRET is not configured; unable to verify Nodeless callback')
×
UNCOV
37
      response
×
38
        .status(500)
39
        .setHeader('content-type', 'application/json; charset=utf8')
40
        .send('{"status":"error","message":"Internal Server Error"}')
UNCOV
41
      return
×
42
    }
43

UNCOV
44
    const signatureValidation = validateSchema(nodelessSignatureSchema)(request.headers['nodeless-signature'])
×
UNCOV
45
    if (signatureValidation.error) {
×
UNCOV
46
      logger('nodeless callback request rejected: invalid signature format')
×
UNCOV
47
      response
×
48
        .status(400)
49
        .setHeader('content-type', 'application/json; charset=utf8')
50
        .send('{"status":"error","message":"Invalid signature"}')
UNCOV
51
      return
×
52
    }
53

UNCOV
54
    const expectedBuf = hmacSha256(webhookSecret, (request as any).rawBody)
×
UNCOV
55
    const actualBuf = Buffer.from(signatureValidation.value, 'hex')
×
56

UNCOV
57
    if (!timingSafeEqual(expectedBuf, actualBuf)) {
×
UNCOV
58
      logger('nodeless callback request rejected: signature mismatch')
×
UNCOV
59
      response.status(403).send('Forbidden')
×
UNCOV
60
      return
×
61
    }
62

UNCOV
63
    const nodelessInvoice = applySpec({
×
64
      id: prop('uuid'),
65
      status: prop('status'),
66
      satsAmount: prop('amount'),
67
      metadata: prop('metadata'),
68
      paidAt: ifElse(propEq('status', 'paid'), always(new Date().toISOString()), always(null)),
69
      createdAt: ifElse(propSatisfies(is(String), 'createdAt'), prop('createdAt'), path(['metadata', 'createdAt'])),
70
    })(request.body)
71

UNCOV
72
    logger('nodeless invoice: %O', nodelessInvoice)
×
73

UNCOV
74
    const invoice = fromNodelessInvoice(nodelessInvoice)
×
75

UNCOV
76
    logger('invoice: %O', invoice)
×
77

78
    let updatedInvoice: Invoice
UNCOV
79
    try {
×
UNCOV
80
      updatedInvoice = await this.paymentsService.updateInvoiceStatus(invoice)
×
UNCOV
81
      logger('updated invoice: %O', updatedInvoice)
×
82
    } catch (error) {
UNCOV
83
      logger.error(`Unable to persist invoice ${invoice.id}`, error)
×
84

UNCOV
85
      throw error
×
86
    }
87

UNCOV
88
    if (updatedInvoice.status !== InvoiceStatus.COMPLETED && !updatedInvoice.confirmedAt) {
×
UNCOV
89
      response.status(200).send()
×
90

UNCOV
91
      return
×
92
    }
93

UNCOV
94
    invoice.amountPaid = invoice.amountRequested
×
UNCOV
95
    updatedInvoice.amountPaid = invoice.amountRequested
×
96

UNCOV
97
    try {
×
UNCOV
98
      await this.paymentsService.confirmInvoice(invoice)
×
UNCOV
99
      await this.paymentsService.sendInvoiceUpdateNotification(updatedInvoice)
×
100
    } catch (error) {
UNCOV
101
      logger.error(`Unable to confirm invoice ${invoice.id}`, error)
×
102

UNCOV
103
      throw error
×
104
    }
105

UNCOV
106
    response.status(200).setHeader('content-type', 'application/json; charset=utf8').send('{"status":"ok"}')
×
107
  }
108
}
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