mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Compare commits
7 commits
v2.1.11-pr
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3bcbae1a32 | ||
|
|
180801223d | ||
|
|
07eb1520dc | ||
|
|
f814ac47c5 | ||
|
|
69a43ef1ea | ||
|
|
6755046e4b | ||
|
|
8843e2f064 |
19 changed files with 942 additions and 693 deletions
40
cypress.config.js
Normal file
40
cypress.config.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const { defineConfig } = require('cypress')
|
||||
const setupNodeEvents = require('./cypress/plugins')
|
||||
|
||||
const specPattern = [
|
||||
'cypress/integration/spec_empty_specter_home.js',
|
||||
'cypress/integration/spec_connections.js',
|
||||
'cypress/integration/spec_devices.js',
|
||||
'cypress/integration/spec_ghost_machine.js',
|
||||
'cypress/integration/spec_fees.js',
|
||||
'cypress/integration/spec_rescan.js',
|
||||
'cypress/integration/spec_qr_signing.js',
|
||||
'cypress/integration/spec_labeling.js',
|
||||
'cypress/integration/spec_balances_amounts.js',
|
||||
'cypress/integration/spec_wallet_send.js',
|
||||
'cypress/integration/spec_wallet_utxo.js',
|
||||
'cypress/integration/spec_plugins.js',
|
||||
]
|
||||
|
||||
module.exports = defineConfig({
|
||||
e2e: {
|
||||
baseUrl: 'http://localhost:25444',
|
||||
specPattern,
|
||||
supportFile: 'cypress/support/index.js',
|
||||
testIsolation: false,
|
||||
setupNodeEvents,
|
||||
},
|
||||
env: {
|
||||
broadcast_timeout: 8000,
|
||||
},
|
||||
includeShadowDom: false,
|
||||
reporter: 'junit',
|
||||
reporterOptions: {
|
||||
mochaFile: 'cypresstest-output.xml',
|
||||
toConsole: true,
|
||||
},
|
||||
retries: {
|
||||
runMode: 1,
|
||||
openMode: 0,
|
||||
},
|
||||
})
|
||||
|
|
@ -29,8 +29,15 @@ describe('Completely empty specter-home', () => {
|
|||
})
|
||||
|
||||
it('Login with password and deactivate password protection again', () => {
|
||||
cy.get('[data-cy="admin-password"]').type('satoshi')
|
||||
cy.get('[data-cy="login-btn"]').click()
|
||||
cy.get('body').then(($body) => {
|
||||
// Cypress 13 keeps browser context when testIsolation is disabled for this
|
||||
// stateful suite. If the session cookie survived from the previous test,
|
||||
// we are already logged in and can go straight to settings.
|
||||
if ($body.find('[data-cy="admin-password"]').length) {
|
||||
cy.get('[data-cy="admin-password"]').type('satoshi')
|
||||
cy.get('[data-cy="login-btn"]').click()
|
||||
}
|
||||
})
|
||||
cy.get('[data-cy="settings-btn"]').click()
|
||||
cy.contains('Authentication').click()
|
||||
cy.get('[data-cy="authentication-selection"]').select('None')
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ describe('Test the fee UI', () => {
|
|||
cy.intercept('POST', '/wallets/wallet/ghost_wallet/estimate_fee', {fee: 0.00005944})
|
||||
cy.get('[data-cy="show-estimated-fee-btn"]').click()
|
||||
cy.contains('You provided no address')
|
||||
cy.get('#recipient_0').find('#address').invoke('val', "bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs")
|
||||
cy.get('#recipient_0').find('#address').invoke('val', "bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs").trigger('input')
|
||||
cy.get('[data-cy="sats-checkbox"]').click()
|
||||
cy.get('#recipient_0').find('[data-cy="amount-input"]').type(50000, { force: true })
|
||||
cy.get('[data-cy="show-estimated-fee-btn"]').click()
|
||||
|
|
|
|||
|
|
@ -5,10 +5,8 @@ describe('Test the labeling of addresses and transactions', () => {
|
|||
cy.visit('/')
|
||||
})
|
||||
|
||||
// Keeps the session cookie alive, Cypress by default clears all cookies before each test
|
||||
beforeEach(() => {
|
||||
cy.viewport('macbook-13')
|
||||
Cypress.Cookies.preserveOnce('session')
|
||||
})
|
||||
|
||||
it('Labeling an address on the address overview', () => {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,16 @@ describe('Test plugins', () => {
|
|||
})
|
||||
|
||||
it('Deactivate password protection', () => {
|
||||
// This flow only works if we don't keep the session alive! So, no Cypress.Cookies.preserveOnce('session') in beforeEach().
|
||||
cy.get('#password').type("mySecretPassword")
|
||||
cy.get('#login-btn').click()
|
||||
cy.get('body').then(($body) => {
|
||||
// Cypress 13 keeps browser context when testIsolation is disabled for
|
||||
// this stateful suite. If the session cookie survived from the
|
||||
// previous test, we are already logged in and can go straight to
|
||||
// settings; otherwise log in first.
|
||||
if ($body.find('#password').length) {
|
||||
cy.get('#password').type("mySecretPassword")
|
||||
cy.get('#login-btn').click()
|
||||
}
|
||||
})
|
||||
cy.get('[data-cy="settings-btn"]').click()
|
||||
cy.get('[href="/settings/auth"]').click()
|
||||
cy.get('select').select("none")
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import './commands'
|
|||
// Set the view port for each test
|
||||
beforeEach(() => {
|
||||
cy.viewport('macbook-13')
|
||||
cy.session('preserveSession', () => {}) // Keeps the session cookie alive, Cypress by default clears all cookies before each test
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
707
package-lock.json
generated
707
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,7 +13,7 @@
|
|||
"wait-on": "^9.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cypress": "^9.7.0",
|
||||
"cypress": "^13.17.0",
|
||||
"cypress-wait-until": "^1.7.1",
|
||||
"gulp": "^5.0.1",
|
||||
"tailwindcss": "^3.2.4"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
pyinstaller==6.11.1
|
||||
pyinstaller-hooks-contrib>=2024.0
|
||||
setuptools>=78.1.1
|
||||
backports.tarfile==1.2.0
|
||||
|
||||
# Platform helpers
|
||||
altgraph>=0.17
|
||||
|
|
|
|||
|
|
@ -76,17 +76,17 @@ dependencies = {file = ["requirements.in"]}
|
|||
|
||||
[project.optional-dependencies]
|
||||
test = [
|
||||
"black==22.3.0",
|
||||
"black==26.3.1",
|
||||
"pre-commit==2.13.0",
|
||||
"pip-tools==6.13",
|
||||
"pytest==7.1.2",
|
||||
"pytest==9.0.3",
|
||||
"PySocks==1.7.1",
|
||||
"pytest-cov==2.10.1",
|
||||
"mock==4.0.2",
|
||||
# requirements for stuff in ./utils
|
||||
"requests==2.31.0",
|
||||
"requests==2.33.0",
|
||||
]
|
||||
|
||||
gendownloadpage = [
|
||||
"markdown==3.3.7"
|
||||
"markdown==3.8.1"
|
||||
]
|
||||
|
|
@ -1,14 +1,16 @@
|
|||
certifi==2024.7.4
|
||||
Click==8.1.7
|
||||
Flask==2.2.5
|
||||
# Flask 2.2.x test_client uses werkzeug.__version__, removed in Werkzeug 3.1.
|
||||
Werkzeug==3.0.6
|
||||
Flask-Babel==3.1.0
|
||||
Flask-Cors==6.0.0
|
||||
Flask-Login==0.6.3
|
||||
Flask-RESTful==0.3.10
|
||||
Flask-HTTPAuth==4.8.0
|
||||
Flask-HTTPAuth==4.8.1
|
||||
hwi==3.1.0
|
||||
python-dotenv==0.21.1
|
||||
requests==2.31.0
|
||||
python-dotenv==1.2.2
|
||||
requests==2.33.0
|
||||
pysocks==1.7.1
|
||||
six==1.16.0
|
||||
stem==1.8.0
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
# This file is autogenerated by pip-compile with Python 3.10
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --cert=None --client-cert=None --generate-hashes --index-url=None --pip-args=None requirements.in
|
||||
# pip-compile --generate-hashes requirements.in
|
||||
#
|
||||
aioitertools==0.11.0 \
|
||||
--hash=sha256:04b95e3dab25b449def24d7df809411c10e62aab0cbe31a50ca4e68748c43394 \
|
||||
|
|
@ -267,9 +267,9 @@ flask-cors==6.0.0 \
|
|||
--hash=sha256:4592c1570246bf7beee96b74bc0adbbfcb1b0318f6ba05c412e8909eceec3393 \
|
||||
--hash=sha256:6332073356452343a8ccddbfec7befdc3fdd040141fe776ec9b94c262f058657
|
||||
# via -r requirements.in
|
||||
flask-httpauth==4.8.0 \
|
||||
--hash=sha256:66568a05bc73942c65f1e2201ae746295816dc009edd84b482c44c758d75097a \
|
||||
--hash=sha256:a58fedd09989b9975448eef04806b096a3964a7feeebc0a78831ff55685b62b0
|
||||
flask-httpauth==4.8.1 \
|
||||
--hash=sha256:0080393d70e12327781f7509115175ec5e47209816489a620d4fd39e20cea2e8 \
|
||||
--hash=sha256:88499b22f1353893743c3cd68f2ca561c4ad9ef75cd6bcc7f621161cd0e80744
|
||||
# via -r requirements.in
|
||||
flask-login==0.6.3 \
|
||||
--hash=sha256:5e23d14a607ef12806c699590b89d0f0e0d67baeec599d75947bf9c147330333 \
|
||||
|
|
@ -744,9 +744,9 @@ psycopg2-binary==2.9.5 \
|
|||
pyaes==1.6.1 \
|
||||
--hash=sha256:02c1b1405c38d3c370b085fb952dd8bea3fadcee6411ad99f312cc129c536d8f
|
||||
# via hwi
|
||||
pyasn1==0.6.3 \
|
||||
--hash=sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf \
|
||||
--hash=sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde
|
||||
pyasn1==0.6.4 \
|
||||
--hash=sha256:9c447d8431c947fe4c8febc4ed9e760bc29011a5b01e5c74b67025bd9fb8ce81 \
|
||||
--hash=sha256:deda9277cfd454080ec40b207fb6df82206a3a2688735233cdcd8d3d565f088b
|
||||
# via pgpy
|
||||
pycparser==2.22 \
|
||||
--hash=sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6 \
|
||||
|
|
@ -777,9 +777,9 @@ python-dateutil==2.9.0.post0 \
|
|||
# via
|
||||
# flask-apscheduler
|
||||
# pandas
|
||||
python-dotenv==0.21.1 \
|
||||
--hash=sha256:1c93de8f636cde3ce377292818d0e440b6e45a82f215c3744979151fa8151c49 \
|
||||
--hash=sha256:41e12e0318bebc859fcc4d97d4db8d20ad21721a6aa5047dd59f090391cb549a
|
||||
python-dotenv==1.2.2 \
|
||||
--hash=sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a \
|
||||
--hash=sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3
|
||||
# via -r requirements.in
|
||||
pytimeparse==1.1.8 \
|
||||
--hash=sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd \
|
||||
|
|
@ -793,9 +793,9 @@ pytz==2024.1 \
|
|||
# flask-babel
|
||||
# flask-restful
|
||||
# pandas
|
||||
requests==2.31.0 \
|
||||
--hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \
|
||||
--hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1
|
||||
requests==2.33.0 \
|
||||
--hash=sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b \
|
||||
--hash=sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652
|
||||
# via
|
||||
# -r requirements.in
|
||||
# cryptoadvance-spectrum
|
||||
|
|
@ -907,10 +907,11 @@ urllib3==2.7.0 \
|
|||
--hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
|
||||
--hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
|
||||
# via requests
|
||||
werkzeug==3.0.3 \
|
||||
--hash=sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18 \
|
||||
--hash=sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8
|
||||
werkzeug==3.0.6 \
|
||||
--hash=sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17 \
|
||||
--hash=sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d
|
||||
# via
|
||||
# -r requirements.in
|
||||
# flask
|
||||
# flask-cors
|
||||
# flask-login
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
""" Security Related things for the REST-API """
|
||||
"""Security Related things for the REST-API"""
|
||||
|
||||
import logging, jwt
|
||||
from functools import wraps
|
||||
|
||||
|
|
@ -14,6 +15,18 @@ from flask import current_app as app
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@auth.error_handler
|
||||
@token_auth.error_handler
|
||||
def auth_error(status):
|
||||
"""Return REST API authentication failures as JSON.
|
||||
|
||||
Flask-HTTPAuth 4.8.1 changed the default unauthorized response body to
|
||||
plain text ("Unauthorized Access"). The REST API has historically returned
|
||||
Flask-RESTful JSON error objects, and clients/tests rely on that shape.
|
||||
"""
|
||||
return abort(status)
|
||||
|
||||
|
||||
@auth.verify_password
|
||||
def verify_password(username, password):
|
||||
"""Validate user passwords and store user in the 'g' object"""
|
||||
|
|
|
|||
|
|
@ -354,6 +354,40 @@ class Wallet(AbstractWallet):
|
|||
res = wallet_rpc.importdescriptors(args)
|
||||
else:
|
||||
res = wallet_rpc.importmulti(args, {"rescan": False})
|
||||
if not all([r["success"] for r in res]):
|
||||
# Core refuses to shrink an existing keypool range on
|
||||
# (re-)import: "new range must include current range".
|
||||
# This can happen when re-creating a wallet under a name
|
||||
# that Core already has a wider range recorded for (e.g.
|
||||
# after a keypoolrefill). Parse the range Core is telling
|
||||
# us about and retry once with a range wide enough to
|
||||
# include it (see #2604).
|
||||
widened_range = None
|
||||
for r in res:
|
||||
if r["success"]:
|
||||
continue
|
||||
match = re.search(
|
||||
r"current range = \[(\d+),\s*(\d+)\]",
|
||||
r.get("error", {}).get("message", ""),
|
||||
)
|
||||
if not match:
|
||||
continue
|
||||
current_start, current_end = int(match.group(1)), int(
|
||||
match.group(2)
|
||||
)
|
||||
start = min(0, current_start)
|
||||
end = max(cls.GAP_LIMIT, current_end)
|
||||
if widened_range is None:
|
||||
widened_range = [start, end]
|
||||
else:
|
||||
widened_range = [
|
||||
min(widened_range[0], start),
|
||||
max(widened_range[1], end),
|
||||
]
|
||||
if widened_range is not None:
|
||||
for arg in args:
|
||||
arg["range"] = widened_range
|
||||
res = wallet_rpc.importmulti(args, {"rescan": False})
|
||||
|
||||
if not all([r["success"] for r in res]):
|
||||
all_issues = " and ".join(
|
||||
|
|
|
|||
177
tests/test_wallet_create_range.py
Normal file
177
tests/test_wallet_create_range.py
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
"""
|
||||
Regression tests for https://github.com/cryptoadvance/specter-desktop/issues/2604
|
||||
|
||||
Bitcoin Core's legacy `importmulti` refuses to shrink the keypool range of an
|
||||
already-imported descriptor ("new range must include current range"). This can
|
||||
surface when re-creating a wallet under a name that Core already has a wider
|
||||
range recorded for. Wallet.create should detect that specific rejection and
|
||||
retry once with a range wide enough to include Core's current range, instead
|
||||
of failing outright.
|
||||
|
||||
These tests mock the RPC layer entirely, so they don't need a running
|
||||
bitcoind/regtest node.
|
||||
"""
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from cryptoadvance.specter.key import Key
|
||||
from cryptoadvance.specter.wallet import Wallet
|
||||
|
||||
|
||||
def _make_rpc(importmulti_responses):
|
||||
"""Builds a fake `rpc`/`wallet_rpc` pair.
|
||||
|
||||
importmulti_responses: a list of return values, one per call to
|
||||
`importmulti`. Each element is itself the list-of-dicts the real RPC
|
||||
would return.
|
||||
"""
|
||||
wallet_rpc = MagicMock()
|
||||
wallet_rpc.importmulti.side_effect = importmulti_responses
|
||||
|
||||
rpc = MagicMock()
|
||||
rpc.getnetworkinfo.return_value = {"version": 200000} # pre-descriptor-wallets
|
||||
rpc.wallet.return_value = wallet_rpc
|
||||
return rpc, wallet_rpc
|
||||
|
||||
|
||||
class _BareWallet(Wallet):
|
||||
"""A stand-in for Wallet that skips the real __init__ (which touches
|
||||
rpc, address lists, tx history, etc.) so these tests only exercise the
|
||||
`create()` classmethod's import/retry logic."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.init_args = args
|
||||
self.init_kwargs = kwargs
|
||||
|
||||
|
||||
def _single_sig_key():
|
||||
# A known-good testnet tpub (m/84'/1'/0') used elsewhere in the test
|
||||
# suite's ghost-machine fixtures.
|
||||
tpub = "tpubDC4DsqH5rqHqipMNqUbDFtQT3AkKkUrvLsN6miySvortU3s1LGaNVAb7wX2No2VsuxQV82T8s3HJLv3kdx1CPjsJ3onC1Zo5mWCQzRVaWVX"
|
||||
return Key.parse_xpub(f"[81f802e3/84h/1h/0h]{tpub}")
|
||||
|
||||
|
||||
def test_create_retries_with_widened_range_on_shrink_refusal():
|
||||
"""Core rejects the default [0, GAP_LIMIT] range because it already has
|
||||
a wider range on file; Wallet.create should retry with a widened range
|
||||
and succeed."""
|
||||
key = _single_sig_key()
|
||||
|
||||
shrink_refusal = [
|
||||
{
|
||||
"success": False,
|
||||
"error": {"message": "new range must include current range = [0,1008]"},
|
||||
},
|
||||
{
|
||||
"success": False,
|
||||
"error": {"message": "new range must include current range = [0,1000]"},
|
||||
},
|
||||
]
|
||||
success = [{"success": True}, {"success": True}]
|
||||
|
||||
rpc, wallet_rpc = _make_rpc([shrink_refusal, success])
|
||||
|
||||
wallet = _BareWallet.create(
|
||||
rpc=rpc,
|
||||
rpc_path="specter",
|
||||
working_folder="/tmp",
|
||||
device_manager=MagicMock(),
|
||||
wallet_manager=MagicMock(),
|
||||
name="mywallet",
|
||||
alias="mywallet",
|
||||
sigs_required=1,
|
||||
key_type="wpkh",
|
||||
keys=[key],
|
||||
devices=[MagicMock()],
|
||||
core_version=200000,
|
||||
)
|
||||
|
||||
assert wallet is not None
|
||||
# One retry: the first call (default range) failed, the second (widened
|
||||
# range) succeeded.
|
||||
assert wallet_rpc.importmulti.call_count == 2
|
||||
second_call_args = wallet_rpc.importmulti.call_args_list[1][0][0]
|
||||
# Widened range must cover the widest range Core reported (1008)
|
||||
assert all(arg["range"] == [0, 1008] for arg in second_call_args)
|
||||
|
||||
|
||||
def test_create_raises_if_retry_also_fails():
|
||||
"""If the widened-range retry still fails, Wallet.create should still
|
||||
raise a SpecterError (no silent swallow of a real problem)."""
|
||||
from cryptoadvance.specter.specter_error import SpecterError
|
||||
|
||||
key = _single_sig_key()
|
||||
|
||||
shrink_refusal = [
|
||||
{
|
||||
"success": False,
|
||||
"error": {"message": "new range must include current range = [0,1008]"},
|
||||
},
|
||||
]
|
||||
still_failing = [
|
||||
{
|
||||
"success": False,
|
||||
"error": {"message": "some other unrelated error"},
|
||||
},
|
||||
]
|
||||
|
||||
rpc, wallet_rpc = _make_rpc([shrink_refusal, still_failing])
|
||||
|
||||
try:
|
||||
_BareWallet.create(
|
||||
rpc=rpc,
|
||||
rpc_path="specter",
|
||||
working_folder="/tmp",
|
||||
device_manager=MagicMock(),
|
||||
wallet_manager=MagicMock(),
|
||||
name="mywallet",
|
||||
alias="mywallet",
|
||||
sigs_required=1,
|
||||
key_type="wpkh",
|
||||
keys=[key],
|
||||
devices=[MagicMock()],
|
||||
core_version=200000,
|
||||
)
|
||||
assert False, "expected SpecterError"
|
||||
except SpecterError:
|
||||
pass
|
||||
|
||||
assert wallet_rpc.importmulti.call_count == 2
|
||||
|
||||
|
||||
def test_create_does_not_retry_on_unrelated_failure():
|
||||
"""A failure that isn't the range-shrink message should raise immediately,
|
||||
without a pointless retry."""
|
||||
from cryptoadvance.specter.specter_error import SpecterError
|
||||
|
||||
key = _single_sig_key()
|
||||
|
||||
unrelated_failure = [
|
||||
{
|
||||
"success": False,
|
||||
"error": {"message": "some other unrelated error"},
|
||||
},
|
||||
]
|
||||
|
||||
rpc, wallet_rpc = _make_rpc([unrelated_failure])
|
||||
|
||||
try:
|
||||
_BareWallet.create(
|
||||
rpc=rpc,
|
||||
rpc_path="specter",
|
||||
working_folder="/tmp",
|
||||
device_manager=MagicMock(),
|
||||
wallet_manager=MagicMock(),
|
||||
name="mywallet",
|
||||
alias="mywallet",
|
||||
sigs_required=1,
|
||||
key_type="wpkh",
|
||||
keys=[key],
|
||||
devices=[MagicMock()],
|
||||
core_version=200000,
|
||||
)
|
||||
assert False, "expected SpecterError"
|
||||
except SpecterError:
|
||||
pass
|
||||
|
||||
assert wallet_rpc.importmulti.call_count == 1
|
||||
|
|
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
|||
@click.option("--delimiter", default=",", help="the delimiter")
|
||||
@click.argument("spec_file")
|
||||
def execute(debug, run, delimiter, spec_file):
|
||||
with open("cypress.json") as json_file:
|
||||
with open("cypress-tests.json") as json_file:
|
||||
data = json.load(json_file)
|
||||
|
||||
spec_create_list = []
|
||||
|
|
|
|||
|
|
@ -240,7 +240,7 @@ function restore_snapshot {
|
|||
# Checking whether spec-files exists
|
||||
if ! [ -f ./cypress/integration/${spec_file} ]; then
|
||||
echo "Spec-file $spec_file does not exist, these are the options:"
|
||||
cat cypress.json | jq -r ".testFiles[]"
|
||||
cat cypress-tests.json | jq -r ".testFiles[]"
|
||||
exit 1
|
||||
fi
|
||||
snapshot_file=./cypress/fixtures/${spec_file}_btcdir.tar.gz
|
||||
|
|
@ -341,7 +341,11 @@ function sub_run {
|
|||
start_bitcoind --reset
|
||||
start_elementsd --reset
|
||||
start_specter --reset
|
||||
npx cypress run
|
||||
# Cypress 10+ normalizes specs discovered from config (often alphabetically),
|
||||
# but this suite is intentionally stateful and depends on the order in
|
||||
# cypress-tests.json. Pass an explicit --spec list so a full run keeps the
|
||||
# legacy order from cypress.json/testFiles.
|
||||
npx cypress run --spec "$(./utils/calc_cypress_test_spec.py --run spec_empty_specter_home.js)"
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -358,7 +362,7 @@ function sub_snapshot {
|
|||
# We'll create a snapshot BEFORE this spec-file has been tested:
|
||||
if [ ! -f ./cypress/integration/$spec_file ]; then
|
||||
echo "ERROR: Use one of these arguments:"
|
||||
cat cypress.json | jq -r ".testFiles[]"
|
||||
cat cypress-tests.json | jq -r ".testFiles[]"
|
||||
exit 2
|
||||
fi
|
||||
start_bitcoind --reset
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue