source/projects/octorest/test/test_client.py
2025-02-06 01:55:09 -07:00

562 lines
20 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import time
import os
from itertools import chain, combinations
import pytest
import os
from octorest import OctoRest
from betamax import Betamax
from betamax_serializers import pretty_json
from _common import URL, APIKEY
with Betamax.configure() as config:
config.cassette_library_dir = 'tests/fixtures/cassettes'
record_mode = os.environ.get('RECORD', 'none')
config.default_cassette_options['record_mode'] = record_mode
config.default_cassette_options['match_requests_on'] = {
'uri',
'method',
}
Betamax.register_serializer(pretty_json.PrettyJSONSerializer)
config.default_cassette_options['serialize_with'] = 'prettyjson'
def sleep(seconds):
'''
If recording, sleep for a given amount of seconds
'''
# if 'RECORD' in os.environ:
time.sleep(seconds)
def cmd_wait(client, state):
while client.state() == state:
sleep(0.1)
def cmd_wait_until(client, state):
while client.state() != state:
sleep(0.1)
def subsets(*items):
'''
Get all possible subsets of something
'''
N = len(items)+1
return chain(*map(lambda x: combinations(items, x), range(0, N)))
def zero(component):
'''
Add a 0 at the end of the component, if it is tool
'''
return 'tool0' if component == 'tool' else component
# @pytest.mark.usefixtures('betamax_session')
@pytest.fixture
def client():
return OctoRest(url=URL, apikey=APIKEY, session=None)
@pytest.fixture
def gcode():
class GCode:
def __init__(self, filename):
self.filename = filename
self.path = 'tests/fixtures/gcodes/{}'.format(filename)
return GCode('telephonebox.gcode')
class TestClient:
@pytest.mark.usefixtures('betamax_session')
def test_init_works_with_good_auth(self):
# Should not raise anything
OctoRest(url=URL, apikey=APIKEY)
@pytest.mark.usefixtures('betamax_session')
def test_init_raises_with_bad_auth(self):
with pytest.raises(RuntimeError):
OctoRest(url=URL, apikey='nope')
### VERSION INFORMATION TESTS ###
def test_version(self, client):
version = client.get_version()
assert 'api' in version
assert 'server' in version
assert 'text' in version
### FILE OPERATION TESTS ###
def test_files_contains_files_and_free_space_info(self, client):
files = client.files()
assert 'bigben.gcode' in [f['name'] for f in files['files']]
assert isinstance(files['free'], int)
def test_files_local_works(self, client):
files = client.files('local')
assert 'bigben.gcode' in [f['name'] for f in files['files']]
assert isinstance(files['free'], int)
def test_files_sdcard_works(self, client):
files = client.files('sdcard')
assert files['files'] == [] # no files on sdcard
assert 'free' not in files # API doesn't report that back
@pytest.mark.parametrize('filename', ('bigben.gcode', 'stpauls.gcode'))
def test_info_for_specific_file(self, client, filename):
f = client.files(filename)
assert f['name'] == filename
@pytest.mark.parametrize('filename', ('unicorn.gcode', 'yeti.gcode', 'noexist.gcode'))
def test_nonexisting_file_raises(self, client, filename):
with pytest.raises(RuntimeError):
client.files(filename)
def test_upload_by_path(self, client, gcode):
f = client.upload(gcode.path)
assert f['done']
assert f['files']['local']['name'] == gcode.filename
client.delete(gcode.filename)
def test_upload_file_object(self, client, gcode):
with open(gcode.path) as fo:
f = client.upload(('fake.gcode', fo))
assert f['done']
assert f['files']['local']['name'] == 'fake.gcode'
client.delete('fake.gcode')
def test_upload_and_select(self, client, gcode):
f = client.upload(gcode.path, select=True)
assert f['done']
assert f['files']['local']['name'] == gcode.filename
selected = client.job_info()['job']['file']['name']
assert selected == gcode.filename
client.delete(gcode.filename)
def test_upload_and_print(self, client, gcode):
f = client.upload(gcode.path, print=True)
sleep(1)
assert f['done']
assert f['files']['local']['name'] == gcode.filename
selected = client.job_info()['job']['file']['name']
assert selected == gcode.filename
assert client.state() == 'Printing'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_upload_and_select_one_by_one(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename)
selected = client.job_info()['job']['file']['name']
assert selected == gcode.filename
client.delete(gcode.filename)
def test_upload_and_select_with_print_one_by_one(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename, print=True)
sleep(1)
selected = client.job_info()['job']['file']['name']
assert selected == gcode.filename
assert client.state() == 'Printing'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_upload_and_select_and_print_one_by_one(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename)
selected = client.job_info()['job']['file']['name']
assert selected == gcode.filename
client.start()
sleep(1)
assert client.state() == 'Printing'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_file_copy(self, client, gcode):
client.upload(gcode.path)
client.copy(gcode.filename, 'copied.gcode')
files = client.files()
assert gcode.filename in [f['name'] for f in files['files']]
assert 'copied.gcode' in [f['name'] for f in files['files']]
client.delete(gcode.filename)
client.delete('copied.gcode')
def test_file_copy_exists(self, client, gcode):
client.upload(gcode.path)
client.copy(gcode.filename, 'copied.gcode')
files = client.files()
assert gcode.filename in [f['name'] for f in files['files']]
assert 'copied.gcode' in [f['name'] for f in files['files']]
with pytest.raises(RuntimeError):
client.copy(gcode.filename, 'copied.gcode')
client.delete(gcode.filename)
def test_file_copy_folder_not_exist(self, client, gcode):
files = client.files()
if 'copied.gcode' in [f['name'] for f in files['files']]:
client.delete('copied.gcode')
client.upload(gcode.path)
with pytest.raises(RuntimeError):
client.copy(gcode.filename, '/random/path/copied.gcode')
client.delete(gcode.filename)
def test_file_move(self, client, gcode):
client.upload(gcode.path)
client.move(gcode.filename, 'moved.gcode')
files = client.files()
assert 'moved.gcode' in [f['name'] for f in files['files']]
client.delete('moved.gcode')
def test_file_move_exists(self, client, gcode):
client.upload(gcode.path)
client.move(gcode.filename, 'moved.gcode')
files = client.files()
assert 'moved.gcode' in [f['name'] for f in files['files']]
client.upload(gcode.path)
with pytest.raises(RuntimeError):
client.move(gcode.filename, 'moved.gcode')
client.delete(gcode.filename)
client.delete('moved.gcode')
def test_file_move_folder_not_exist(self, client, gcode):
client.upload(gcode.path)
with pytest.raises(RuntimeError):
client.copy(gcode.filename, '/random/path/moved.gcode')
client.delete(gcode.filename)
def test_slice_curalegacy(self, client):
client.slice('biscuithelper.STL', slicer='curalegacy')
sleep(2)
files = client.files()
assert 'biscuithelper.gco' in [f['name'] for f in files['files']]
client.delete('biscuithelper.gco')
@pytest.mark.parametrize('name', ('biscuits.gco', 'richtea.gcode'))
def test_slice_curalegacy_gcode(self, client, name):
client.slice('biscuithelper.STL', slicer='curalegacy', gcode=name)
sleep(2)
files = client.files()
assert name in [f['name'] for f in files['files']]
client.delete(name)
def test_slice_curalegacy_select(self, client):
client.slice('biscuithelper.STL', slicer='curalegacy', select=True)
sleep(2)
files = client.files()
assert 'biscuithelper.gco' in [f['name'] for f in files['files']]
selected = client.job_info()['job']['file']['name']
assert selected == 'biscuithelper.gco'
client.delete('biscuithelper.gco')
def test_upload_print_pause_cancel(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename, print=True)
cmd_wait_until(client, 'Printing')
client.pause()
cmd_wait(client, 'Pausing')
assert client.state() == 'Paused'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_upload_print_pause_restart(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename, print=True)
cmd_wait_until(client, 'Printing')
client.pause()
cmd_wait_until(client, 'Paused')
assert client.state() == 'Paused'
client.restart()
cmd_wait_until(client, 'Printing')
assert client.state() == 'Printing'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_upload_print_pause_resume(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename, print=True)
cmd_wait_until(client, 'Printing')
client.pause()
cmd_wait_until(client, 'Paused')
assert client.state() == 'Paused'
client.resume()
cmd_wait_until(client, 'Printing')
assert client.state() == 'Printing'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_upload_print_toggle(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename, print=True)
cmd_wait_until(client, 'Printing')
client.toggle()
cmd_wait_until(client, 'Paused')
assert client.state() == 'Paused'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_upload_print_toggle_toggle(self, client, gcode):
client.upload(gcode.path)
client.select(gcode.filename, print=True)
cmd_wait_until(client, 'Printing')
client.toggle()
cmd_wait_until(client, 'Paused')
assert client.state() == 'Paused'
client.toggle()
cmd_wait_until(client, 'Printing')
assert client.state() == 'Printing'
client.cancel()
cmd_wait(client, 'Cancelling')
client.delete(gcode.filename)
def test_logs(self, client):
logs = client.logs()
assert 'files' in logs
assert 'free' in logs
assert isinstance(logs['free'], int)
def test_delete_log(self, client):
logs = client.logs()
log_lst = [log['name'] for log in logs['files']]
assert log_lst[0] in log_lst
client.delete_log(log_lst[0])
logs = client.logs()
for log in logs['files']:
assert log['name'] != log_lst[0]
def test_printer(self, client):
printer = client.printer()
assert 'ready' in printer['sd']
assert printer['state']['flags']['operational']
assert printer['state']['flags']['ready']
assert not printer['state']['flags']['error']
assert not printer['state']['flags']['printing']
def test_printer_temps(self, client):
printer = client.printer()
cmd_wait_until(client, 'Operational')
assert 'bed' in printer['temperature']
assert 'tool0' in printer['temperature']
assert 'history' not in printer['temperature']
@pytest.mark.parametrize('exclude', subsets('sd', 'temperature', 'state'))
def test_printer_with_excluded_stuff(self, client, exclude):
printer = client.printer(exclude=exclude)
for key in exclude:
assert key not in printer
assert len(printer) == 3 - len(exclude)
def test_printer_with_history(self, client):
printer = client.printer(history=True)
assert isinstance(printer['temperature']['history'], list)
@pytest.mark.parametrize('limit', range(1, 4))
def test_printer_with_history_and_limit(self, client, limit):
printer = client.printer(history=True, limit=limit)
assert len(printer['temperature']['history']) == limit
@pytest.mark.parametrize('key', ('actual', 'target', 'offset'))
@pytest.mark.parametrize('component', ('tool', 'bed'))
def test_tool_and_bed(self, client, key, component):
info = getattr(client, component)() # client.tool() or bed()
assert 'history' not in info
assert isinstance(info[zero(component)][key], (float, int))
# @pytest.mark.parametrize('key', ('actual', 'target'))
# @pytest.mark.parametrize('component', ('tool', 'bed'))
# def test_tool_and_bed_with_history(self, client, key, component):
# # TODO: history is not working with bed or tool, only printer
# info = getattr(client, component)(history=True)
# assert 'history' in info
# for h in info['history']:
# assert isinstance(h[zero(component)][key], (float, int))
# @pytest.mark.parametrize('limit', range(1, 4))
# @pytest.mark.parametrize('component', ('tool', 'bed'))
# def test_tool_and_bed_with_history_limit(self, client, limit, component):
# # TODO: history is not working with bed or tool, only printer
# info = getattr(client, component)(history=True, limit=limit)
# assert len(info['history']) == limit
def test_home_all(self, client):
# we are only testing if no exception occurred, there's no return
client.home()
@pytest.mark.parametrize('axes', (('x',), ('y',), ('z',), ('x', 'y',)))
def test_home_some(self, client, axes):
# we are only testing if no exception occurred, there's no return
client.home(axes)
@pytest.mark.parametrize('coordinates', ((20, 0, 0), (0, 20, 0)))
def test_jog(self, client, coordinates):
# we are only testing if no exception occurred, there's no return
client.jog(*coordinates)
@pytest.mark.parametrize('factor', (100, 50, 150, 0.5, 1.0))
def test_feedrate(self, client, factor):
# we are only testing if no exception occurred, there's no return
client.feedrate(factor)
@pytest.mark.parametrize('how', (200, [200], {'tool0': 200}))
def test_set_tool_temperature_to_200(self, client, how):
client.tool_target(how)
tool = client.tool()
assert tool['tool0']['target'] == 200.0
if 'RECORD' in os.environ:
# Betamax had some problems here
# And we don't do this for testing, but only with actual printer
client.tool_target(0)
# @pytest.mark.parametrize('how', (20, [20], {'tool0': 20}))
# def test_set_tool_offset_to_20(self, client, how):
# client.tool_offset(how)
# tool = client.tool()
# print(tool)
# assert tool['tool0']['offset'] == 20.0
# # TODO: make the above assert work?
# if 'RECORD' in os.environ:
# client.tool_offset(0)
def test_selecting_tool(self, client):
# we are only testing if no exception occurred, there's no return
client.tool_select(0)
def test_extruding(self, client):
# we are only testing if no exception occurred, there's no return
client.extrude(1)
def test_retracting(self, client):
# we are only testing if no exception occurred, there's no return
client.retract(1)
@pytest.mark.parametrize('factor', (100, 75, 125, 0.75, 1.0))
def test_flowrate(self, client, factor):
# we are only testing if no exception occurred, there's no return
client.flowrate(factor)
def test_set_bed_temperature_to_100(self, client):
client.bed_target(100)
bed = client.bed()
assert bed['bed']['target'] == 100.0
if 'RECORD' in os.environ:
client.bed_target(0)
def test_set_bed_offset_to_10(self, client):
client.bed_offset(10)
bed = client.bed()
assert bed['bed']['offset'] == 10.0
if 'RECORD' in os.environ:
client.bed_offset(0)
def test_sd_card_init(self, client):
client.sd_init()
def test_sd_card_refresh(self, client):
client.sd_refresh()
def test_sd_card_release(self, client):
client.sd_release()
def test_sd_card_status(self, client):
sd = client.sd()
# no SD card here, so always not ready
assert sd['ready'] is False
def test_single_gcode_command(self, client):
client.gcode('G28 X')
def test_multiple_gcode_commands_nl(self, client):
client.gcode('G28 X\nG28 Y')
def test_multiple_gcode_commands_list(self, client):
client.gcode(['G28 X', 'G28 Y'])
def test_get_settings(self, client):
settings = client.settings()
assert 'api' in settings
assert 'appearance' in settings
def test_unchanged_settings(self, client):
settings = client.settings()
new_settings = client.settings({})
assert settings['api'] == new_settings['api']
assert settings['appearance'] == new_settings['appearance']
def test_change_settings(self, client):
settings = client.settings()
printer_name = settings['appearance']['name']
test_name = {'appearance': {'name': 'Gandalf'}}
new_settings = client.settings(test_name)
assert new_settings['appearance']['name'] == 'Gandalf'
client.settings({'appearance': {'name': printer_name}})
# def test_tmp_session_key(self, client):
# key = client.tmp_session_key()
# print(key)
def test_users(self, client):
users = client.users()
assert 'users' in users
### CONNECTION HANDLING TESTS ###
def test_connection_info(self, client):
info = client.connection_info()
assert 'current' in info
assert 'baudrate' in info['current']
assert 'port' in info['current']
assert 'state' in info['current']
assert 'options' in info
assert 'baudrates' in info['options']
assert 'ports' in info['options']
def test_fake_ack(self, client):
# we are only testing if no exception occurred, there's no return
client.fake_ack()
def test_disconnect(self, client):
client.disconnect()
assert client.state() in ['Offline', 'Closed']
def test_connect(self, client):
'''
Since it's hard with betamax fixture to check state() multiple times
in one test, this test hopes test_disconnect() was called before it.
It is not possible to run it without it in record mode.
TODO: Fix this
'''
client.connect()
cmd_wait(client, 'Detecting baudrate')
assert client.state() in ['Connecting',
'Operational',
'Opening serial port']
client.disconnect()
assert client.state() in ['Offline', 'Closed']
# import json
# client = OctoRest(url=URL, apikey=APIKEY)
# client.new_folder('hello')
# f = client.files(recursive=False)
# print(json.dumps(f, indent=4))
# g = client.files(recursive=True)
# print(json.dumps(f, indent=4))
# print(f == g)
# print(client.version)
# client.gcode("M106")
# client.gcode("M106 \n G28 X Y Z \n M107")