388 lines
8.9 KiB
JavaScript
388 lines
8.9 KiB
JavaScript
const {expect} = require('@playwright/test')
|
|
|
|
const DEFAULT_MANIFEST_URL =
|
|
'https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json'
|
|
|
|
function apiKeyHeaders(key) {
|
|
return key ? {'X-Api-Key': key} : {}
|
|
}
|
|
|
|
async function expectStatus(response, expected, label) {
|
|
const statuses = Array.isArray(expected) ? expected : [expected]
|
|
if (!statuses.includes(response.status())) {
|
|
const body = await response.text()
|
|
throw new Error(
|
|
`${label || response.url()} expected status ${statuses.join(
|
|
'/'
|
|
)}, got ${response.status()}: ${body.slice(0, 800)}`
|
|
)
|
|
}
|
|
}
|
|
|
|
async function responseJson(response) {
|
|
const text = await response.text()
|
|
if (!text) return null
|
|
try {
|
|
return JSON.parse(text)
|
|
} catch (error) {
|
|
throw new Error(
|
|
`Expected JSON from ${response.url()}: ${text.slice(0, 800)}`
|
|
)
|
|
}
|
|
}
|
|
|
|
async function jsonRequest(context, method, url, options = {}) {
|
|
const response = await context[method](url, {
|
|
data: options.data,
|
|
form: options.form,
|
|
headers: options.headers,
|
|
params: options.params,
|
|
failOnStatusCode: false,
|
|
maxRedirects: options.maxRedirects
|
|
})
|
|
await expectStatus(
|
|
response,
|
|
options.expected ?? 200,
|
|
`${method.toUpperCase()} ${url}`
|
|
)
|
|
return responseJson(response)
|
|
}
|
|
|
|
async function textRequest(context, method, url, options = {}) {
|
|
const response = await context[method](url, {
|
|
data: options.data,
|
|
headers: options.headers,
|
|
params: options.params,
|
|
failOnStatusCode: false,
|
|
maxRedirects: options.maxRedirects
|
|
})
|
|
await expectStatus(
|
|
response,
|
|
options.expected ?? 200,
|
|
`${method.toUpperCase()} ${url}`
|
|
)
|
|
return response.text()
|
|
}
|
|
|
|
async function loginAdmin(context) {
|
|
await jsonRequest(context, 'post', '/api/v1/auth', {
|
|
data: {username: 'admin', password: 'secret1234'}
|
|
})
|
|
}
|
|
|
|
async function initServer(context) {
|
|
const home = await context.get('/', {
|
|
failOnStatusCode: false,
|
|
maxRedirects: 0
|
|
})
|
|
const location = home.headers().location || ''
|
|
if (
|
|
[301, 302, 303, 307, 308].includes(home.status()) &&
|
|
location.includes('first_install')
|
|
) {
|
|
await jsonRequest(context, 'put', '/api/v1/auth/first_install', {
|
|
data: {
|
|
username: 'admin',
|
|
password: 'secret1234',
|
|
password_repeat: 'secret1234'
|
|
}
|
|
})
|
|
}
|
|
|
|
await loginAdmin(context)
|
|
|
|
const wallets = await jsonRequest(context, 'get', '/api/v1/wallets')
|
|
expect(wallets.length).toBeGreaterThan(0)
|
|
const adminWallet = wallets[0]
|
|
|
|
await jsonRequest(context, 'put', '/admin/api/v1/settings', {
|
|
data: {lnbits_callback_url_rules: []}
|
|
})
|
|
await topUpWallet(context, adminWallet.id, 1_000_000)
|
|
|
|
return {
|
|
walletId: adminWallet.id,
|
|
inkey: adminWallet.inkey,
|
|
adminkey: adminWallet.adminkey
|
|
}
|
|
}
|
|
|
|
async function logout(context) {
|
|
await jsonRequest(context, 'post', '/api/v1/auth/logout')
|
|
}
|
|
|
|
async function createAccount(context, name) {
|
|
await textRequest(context, 'get', '/')
|
|
const wallet = await jsonRequest(context, 'post', '/api/v1/account', {
|
|
data: {name}
|
|
})
|
|
await jsonRequest(context, 'post', '/api/v1/auth/usr', {
|
|
data: {usr: wallet.user}
|
|
})
|
|
return {
|
|
userId: wallet.user,
|
|
walletId: wallet.id,
|
|
inkey: wallet.inkey,
|
|
adminkey: wallet.adminkey,
|
|
raw: wallet
|
|
}
|
|
}
|
|
|
|
async function createWallet(context, name) {
|
|
const wallet = await jsonRequest(context, 'post', '/api/v1/wallet', {
|
|
data: {name}
|
|
})
|
|
return {
|
|
userId: wallet.user,
|
|
walletId: wallet.id,
|
|
inkey: wallet.inkey,
|
|
adminkey: wallet.adminkey,
|
|
raw: wallet
|
|
}
|
|
}
|
|
|
|
async function topUpWallet(adminContext, walletId, amount) {
|
|
return jsonRequest(adminContext, 'put', '/users/api/v1/balance', {
|
|
data: {amount: String(amount), id: walletId}
|
|
})
|
|
}
|
|
|
|
async function enableExtension(context, extension) {
|
|
const enabled = await jsonRequest(
|
|
context,
|
|
'put',
|
|
`/api/v1/extension/${extension}/enable`,
|
|
{
|
|
form: {enable: extension}
|
|
}
|
|
)
|
|
if (enabled && Object.prototype.hasOwnProperty.call(enabled, 'is_enabled')) {
|
|
expect(enabled.is_enabled).toBeTruthy()
|
|
}
|
|
return enabled
|
|
}
|
|
|
|
async function disableExtension(context, extension) {
|
|
return jsonRequest(context, 'put', `/api/v1/extension/${extension}/disable`, {
|
|
form: {enable: extension}
|
|
})
|
|
}
|
|
|
|
async function deactivateExtension(context, extension) {
|
|
return jsonRequest(
|
|
context,
|
|
'put',
|
|
`/api/v1/extension/${extension}/deactivate`
|
|
)
|
|
}
|
|
|
|
async function activateExtension(context, extension, expected = 200) {
|
|
return jsonRequest(
|
|
context,
|
|
'put',
|
|
`/api/v1/extension/${extension}/activate`,
|
|
{
|
|
expected
|
|
}
|
|
)
|
|
}
|
|
|
|
async function uninstallExtension(context, extension) {
|
|
const response = await context.delete(`/api/v1/extension/${extension}`, {
|
|
failOnStatusCode: false
|
|
})
|
|
await expectStatus(
|
|
response,
|
|
[200, 204, 404],
|
|
`DELETE /api/v1/extension/${extension}`
|
|
)
|
|
}
|
|
|
|
async function pageStatus(context, path, expected = 200) {
|
|
const response = await context.get(path, {failOnStatusCode: false})
|
|
await expectStatus(response, expected, `GET ${path}`)
|
|
return response
|
|
}
|
|
|
|
async function getWallet(context, inkey) {
|
|
return jsonRequest(context, 'get', '/api/v1/wallet', {
|
|
headers: apiKeyHeaders(inkey)
|
|
})
|
|
}
|
|
|
|
async function getPayments(context, inkey, params) {
|
|
return jsonRequest(context, 'get', '/api/v1/payments', {
|
|
headers: apiKeyHeaders(inkey),
|
|
params
|
|
})
|
|
}
|
|
|
|
async function createInvoice(context, adminkey, amount, memo) {
|
|
return jsonRequest(context, 'post', '/api/v1/payments', {
|
|
headers: apiKeyHeaders(adminkey),
|
|
data: {out: false, amount, memo}
|
|
})
|
|
}
|
|
|
|
async function payInvoice(context, adminkey, bolt11, expected = 201) {
|
|
return jsonRequest(context, 'post', '/api/v1/payments', {
|
|
headers: apiKeyHeaders(adminkey),
|
|
data: {out: true, bolt11},
|
|
expected
|
|
})
|
|
}
|
|
|
|
async function getPayment(context, inkey, paymentHash) {
|
|
return jsonRequest(context, 'get', `/api/v1/payments/${paymentHash}`, {
|
|
headers: apiKeyHeaders(inkey)
|
|
})
|
|
}
|
|
|
|
async function decodePayment(context, data) {
|
|
return jsonRequest(context, 'post', '/api/v1/payments/decode', {
|
|
data: {data}
|
|
})
|
|
}
|
|
|
|
async function lnurlScan(context, lnurl, key) {
|
|
return jsonRequest(
|
|
context,
|
|
'get',
|
|
`/api/v1/lnurlscan/${encodeURIComponent(lnurl)}`,
|
|
{
|
|
headers: apiKeyHeaders(key)
|
|
}
|
|
)
|
|
}
|
|
|
|
async function clearMirror() {
|
|
const response = await fetch(mirrorUrl('/__mirror__/clear'))
|
|
if (response.status !== 204) {
|
|
throw new Error(`Expected mirror clear status 204, got ${response.status}`)
|
|
}
|
|
}
|
|
|
|
async function getMirrorRequests() {
|
|
const response = await fetch(mirrorUrl('/__mirror__/requests'))
|
|
if (!response.ok) {
|
|
throw new Error(`Cannot fetch mirror requests: ${response.status}`)
|
|
}
|
|
return response.json()
|
|
}
|
|
|
|
async function pollPayment(
|
|
context,
|
|
inkey,
|
|
paymentHash,
|
|
predicate,
|
|
timeout = 10_000
|
|
) {
|
|
let last
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
last = await getPayment(context, inkey, paymentHash)
|
|
return predicate(last)
|
|
},
|
|
{timeout}
|
|
)
|
|
.toBeTruthy()
|
|
return last
|
|
}
|
|
|
|
async function pollWalletBalance(
|
|
context,
|
|
inkey,
|
|
expectedBalance,
|
|
timeout = 10_000
|
|
) {
|
|
await expect
|
|
.poll(
|
|
async () => {
|
|
const wallet = await getWallet(context, inkey)
|
|
return wallet.balance
|
|
},
|
|
{timeout}
|
|
)
|
|
.toBe(expectedBalance)
|
|
}
|
|
|
|
async function fetchManifest() {
|
|
const manifestUrl =
|
|
process.env.EXTENSIONS_MANIFEST_URL || DEFAULT_MANIFEST_URL
|
|
const response = await fetch(manifestUrl)
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`Cannot fetch extension manifest ${manifestUrl}: ${response.status}`
|
|
)
|
|
}
|
|
return response.json()
|
|
}
|
|
|
|
async function latestRelease(context, extension) {
|
|
const releases = await jsonRequest(
|
|
context,
|
|
'get',
|
|
`/api/v1/extension/${extension}/releases`
|
|
)
|
|
expect(releases.length).toBeGreaterThan(0)
|
|
return releases
|
|
.slice()
|
|
.sort((a, b) =>
|
|
a.version.localeCompare(b.version, undefined, {numeric: true})
|
|
)
|
|
.at(-1)
|
|
}
|
|
|
|
async function installLatestExtension(context, extension) {
|
|
const release = await latestRelease(context, extension)
|
|
return jsonRequest(context, 'post', '/api/v1/extension', {
|
|
data: {
|
|
ext_id: extension,
|
|
archive: release.archive,
|
|
source_repo: release.source_repo,
|
|
version: release.version
|
|
}
|
|
})
|
|
}
|
|
|
|
function mirrorUrl(path = '') {
|
|
const port = process.env.MIRROR_PORT || '8500'
|
|
return `http://localhost:${port}${path}`
|
|
}
|
|
|
|
module.exports = {
|
|
apiKeyHeaders,
|
|
activateExtension,
|
|
clearMirror,
|
|
createAccount,
|
|
createInvoice,
|
|
createWallet,
|
|
deactivateExtension,
|
|
decodePayment,
|
|
disableExtension,
|
|
enableExtension,
|
|
expectStatus,
|
|
fetchManifest,
|
|
getMirrorRequests,
|
|
getPayment,
|
|
getPayments,
|
|
getWallet,
|
|
initServer,
|
|
installLatestExtension,
|
|
jsonRequest,
|
|
latestRelease,
|
|
lnurlScan,
|
|
loginAdmin,
|
|
logout,
|
|
mirrorUrl,
|
|
pageStatus,
|
|
payInvoice,
|
|
pollPayment,
|
|
pollWalletBalance,
|
|
responseJson,
|
|
textRequest,
|
|
topUpWallet,
|
|
uninstallExtension
|
|
}
|