mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Resolved conflicts
This commit is contained in:
commit
f76ff8703d
12 changed files with 371 additions and 76 deletions
|
|
@ -33,8 +33,14 @@ async function apiCall(url, method, body, ms = 10000) {
|
|||
try {
|
||||
// await the response or error.
|
||||
let response = await timeoutPromise(ms, promise);
|
||||
if (200 <= response.status < 300) {
|
||||
// Request was successful.
|
||||
return response;
|
||||
}
|
||||
// Request encountered an error.
|
||||
let copy = response.clone();
|
||||
if (response.status === 403 && (await copy.json())['code'] === 17) {
|
||||
let code = (await copy.json())['code'];
|
||||
if (response.status === 403 && code === 17) {
|
||||
toast({
|
||||
type: 'error',
|
||||
title: 'WROL Mode is enabled!',
|
||||
|
|
@ -359,17 +365,24 @@ export async function killDownload(download_id) {
|
|||
|
||||
export async function killDownloads() {
|
||||
let response = await apiPost(`${API_URI}/download/kill`);
|
||||
console.log('killDownloads', response);
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function startDownloads() {
|
||||
let response = await apiPost(`${API_URI}/download/enable`);
|
||||
console.log('startDownloads', response);
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function getDownloaders() {
|
||||
let response = await apiGet(`${API_URI}/downloaders`);
|
||||
return await response.json();
|
||||
try {
|
||||
let response = await apiGet(`${API_URI}/downloaders`);
|
||||
return await response.json();
|
||||
} catch (e) {
|
||||
console.log('getDownloaders', e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const replaceFileDatetimes = (files) => {
|
||||
|
|
@ -423,3 +436,54 @@ export async function getVersion() {
|
|||
let data = await response.json();
|
||||
return data['version'];
|
||||
}
|
||||
|
||||
export async function getHotspotStatus() {
|
||||
let response = await apiGet(`${API_URI}/hotspot`);
|
||||
if (response.status === 200) {
|
||||
let {status} = await response.json();
|
||||
return status;
|
||||
} else {
|
||||
let code = (await response.json())['code'];
|
||||
if (response.status === 400 && code === 34) {
|
||||
// Must not be running on a RPi. Don't warn the user.
|
||||
return null;
|
||||
} else {
|
||||
toast({
|
||||
type: 'error',
|
||||
title: "Can't get Hotspot status",
|
||||
description: 'Failed to get Hotspot status. Check client and server logs.',
|
||||
time: 5000,
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function setHotspot(on) {
|
||||
let response;
|
||||
if (on) {
|
||||
response = await apiPost(`${API_URI}/hotspot/on`);
|
||||
} else {
|
||||
response = await apiPost(`${API_URI}/hotspot/off`);
|
||||
}
|
||||
if (response.status === 204) {
|
||||
return null;
|
||||
} else {
|
||||
let code = (await response.json())['code'];
|
||||
if (code === 34) {
|
||||
toast({
|
||||
type: 'error',
|
||||
title: 'Unsupported!',
|
||||
description: 'Hotspot is only supported on a Raspberry Pi!',
|
||||
time: 5000,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
type: 'error',
|
||||
title: 'Error!',
|
||||
description: 'Could not modify hotspot. See server logs.',
|
||||
time: 5000,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,18 +11,18 @@ import {
|
|||
Placeholder,
|
||||
Table
|
||||
} from "semantic-ui-react";
|
||||
import {
|
||||
getConfig,
|
||||
getDownloaders,
|
||||
getDownloads,
|
||||
killDownload,
|
||||
killDownloads,
|
||||
postDownload,
|
||||
saveConfig,
|
||||
startDownloads
|
||||
} from "../api";
|
||||
import {getConfig, getDownloads, killDownload, postDownload, saveConfig} from "../api";
|
||||
import TimezoneSelect from 'react-timezone-select';
|
||||
import {PageContainer, secondsToDate, secondsToFrequency, TabLinks, textEllipsis, WROLModeMessage} from "./Common";
|
||||
import {
|
||||
DisableDownloadsToggle,
|
||||
HotspotToggle,
|
||||
PageContainer,
|
||||
secondsToDate,
|
||||
secondsToFrequency,
|
||||
TabLinks,
|
||||
textEllipsis,
|
||||
WROLModeMessage
|
||||
} from "./Common";
|
||||
import {Route} from "react-router-dom";
|
||||
|
||||
class Settings extends React.Component {
|
||||
|
|
@ -78,6 +78,10 @@ class Settings extends React.Component {
|
|||
return (
|
||||
<Container fluid>
|
||||
<WROLModeMessage content='Settings are disabled because WROL Mode is enabled.'/>
|
||||
|
||||
<HotspotToggle/>
|
||||
|
||||
<Divider/>
|
||||
<p>
|
||||
The global settings for your server.
|
||||
</p>
|
||||
|
|
@ -305,17 +309,6 @@ class Downloads extends React.Component {
|
|||
|
||||
async componentDidMount() {
|
||||
await this.fetchDownloads();
|
||||
await this.fetchStatus();
|
||||
}
|
||||
|
||||
killDownloads = async () => {
|
||||
await killDownloads();
|
||||
this.setState({stopOpen: false}, this.fetchStatus);
|
||||
}
|
||||
|
||||
startDownloads = async () => {
|
||||
await startDownloads();
|
||||
this.setState({stopOpen: false}, this.fetchStatus);
|
||||
}
|
||||
|
||||
closeStop = () => {
|
||||
|
|
@ -335,11 +328,6 @@ class Downloads extends React.Component {
|
|||
});
|
||||
}
|
||||
|
||||
fetchStatus = async () => {
|
||||
let data = await getDownloaders();
|
||||
this.setState({disabled: data['manager_disabled']});
|
||||
}
|
||||
|
||||
render() {
|
||||
let tablePlaceholder = (
|
||||
<Placeholder>
|
||||
|
|
@ -378,7 +366,9 @@ class Downloads extends React.Component {
|
|||
onceTable = (<Table>
|
||||
{stoppableHeader}
|
||||
<Table.Body>
|
||||
{this.state.once_downloads.map((i) => <StoppableRow {...i} fetchDownloads={this.fetchDownloads}/>)}
|
||||
{this.state.once_downloads.map((i) =>
|
||||
<StoppableRow {...i} fetchDownloads={this.fetchDownloads} key={i.id}/>
|
||||
)}
|
||||
</Table.Body>
|
||||
</Table>);
|
||||
}
|
||||
|
|
@ -390,46 +380,15 @@ class Downloads extends React.Component {
|
|||
recurringTable = (<Table>
|
||||
{nonStoppableHeader}
|
||||
<Table.Body>
|
||||
{this.state.recurring_downloads.map((i) => <DownloadRow {...i}/>)}
|
||||
{this.state.recurring_downloads.map((i) => <DownloadRow {...i} key={i.id}/>)}
|
||||
</Table.Body>
|
||||
</Table>);
|
||||
}
|
||||
|
||||
let {stopOpen} = this.state;
|
||||
let allButton = (<>
|
||||
<Button
|
||||
onClick={this.openStop}
|
||||
color='red'
|
||||
>Disable Downloads</Button>
|
||||
<Confirm
|
||||
open={stopOpen}
|
||||
content='Are you sure you want to stop all downloads?'
|
||||
confirmButton='Stop'
|
||||
onCancel={this.closeStop}
|
||||
onConfirm={this.killDownloads}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
if (this.state.disabled === true) {
|
||||
allButton = (<>
|
||||
<Button
|
||||
onClick={this.openStop}
|
||||
color='green'
|
||||
>Enable Downloads</Button>
|
||||
<Confirm
|
||||
open={stopOpen}
|
||||
content='Are you sure you want to start all downloads?'
|
||||
confirmButton='Start'
|
||||
onCancel={this.closeStop}
|
||||
onConfirm={this.startDownloads}
|
||||
/>
|
||||
</>);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<WROLModeMessage content='Downloads are disabled because WROL Mode is enabled.'/>
|
||||
{allButton}
|
||||
<DisableDownloadsToggle/>
|
||||
<Header as='h1'>Downloads</Header>
|
||||
{onceTable}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import React, {useEffect, useState} from "react";
|
||||
import {Card, Container, Form, Icon, Image, Input, Menu, Pagination, Responsive} from 'semantic-ui-react';
|
||||
import {Card, Checkbox, Container, Form, Icon, Image, Input, Menu, Pagination, Responsive} from 'semantic-ui-react';
|
||||
import {Link, NavLink} from "react-router-dom";
|
||||
import Button from "semantic-ui-react/dist/commonjs/elements/Button";
|
||||
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
|
||||
import Message from "semantic-ui-react/dist/commonjs/collections/Message";
|
||||
import {getConfig} from "../api";
|
||||
import {useDownloaders, useHotspot} from "../hooks/customHooks";
|
||||
|
||||
export const API_URI = `http://${window.location.host}/api`;
|
||||
export const VIDEOS_API = `${API_URI}/videos`;
|
||||
|
|
@ -643,3 +644,23 @@ export function CardGroupCentered(props) {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function HotspotToggle() {
|
||||
let {on, setHotspot} = useHotspot();
|
||||
return <Checkbox toggle
|
||||
label='WiFi Hotspot'
|
||||
disabled={on === null}
|
||||
checked={on === true}
|
||||
onChange={(e, data) => setHotspot(data.checked)}
|
||||
/>;
|
||||
}
|
||||
|
||||
export function DisableDownloadsToggle() {
|
||||
let {on, setDownloads} = useDownloaders();
|
||||
return <Checkbox toggle
|
||||
label={on ? 'Downloading Enabled' : 'Downloading Disabled'}
|
||||
disabled={on === null}
|
||||
checked={on === true}
|
||||
onChange={(e, data) => setDownloads(data.checked)}
|
||||
/>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,17 @@
|
|||
import {useEffect, useState} from "react";
|
||||
import {fetchDomains, filesSearch, getFiles, getVersion, searchArchives, searchVideos} from "../api";
|
||||
import {
|
||||
fetchDomains,
|
||||
filesSearch,
|
||||
getDownloaders,
|
||||
getFiles,
|
||||
getHotspotStatus,
|
||||
getVersion,
|
||||
killDownloads,
|
||||
searchArchives,
|
||||
searchVideos,
|
||||
setHotspot,
|
||||
startDownloads
|
||||
} from "../api";
|
||||
import {useHistory} from "react-router-dom";
|
||||
|
||||
export const useSearchParam = (key, defaultValue = null) => {
|
||||
|
|
@ -178,3 +190,60 @@ export const useBrowseFiles = () => {
|
|||
|
||||
return {browseFiles, openFolders, setOpenFolders, fetchFiles};
|
||||
}
|
||||
|
||||
export const useHotspot = () => {
|
||||
const [on, setOn] = useState(null);
|
||||
|
||||
const fetchHotspotStatus = async () => {
|
||||
const status = await getHotspotStatus();
|
||||
if (status === 'connected') {
|
||||
// Hotspot is on.
|
||||
setOn(true);
|
||||
} else if (status === 'disconnected' || status === 'unavailable') {
|
||||
// Hotspot can be turned on.
|
||||
setOn(false);
|
||||
} else {
|
||||
// Hotspot is not supported. API is probably running in a Docker container.
|
||||
setOn(null);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchHotspotStatus();
|
||||
}, []);
|
||||
|
||||
const localSetHotspot = async (on) => {
|
||||
setOn(null);
|
||||
await setHotspot(on);
|
||||
await fetchHotspotStatus();
|
||||
}
|
||||
|
||||
return {on, setOn, setHotspot: localSetHotspot};
|
||||
}
|
||||
|
||||
export const useDownloaders = () => {
|
||||
const [on, setOn] = useState(null);
|
||||
const [downloaders, setDownloaders] = useState(null);
|
||||
|
||||
const fetchDownloaders = async () => {
|
||||
let data = await getDownloaders();
|
||||
setDownloaders(data['downloaders']);
|
||||
setOn(!data['manager_disabled']);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchDownloaders();
|
||||
}, []);
|
||||
|
||||
const localSetDownloads = async (on) => {
|
||||
setOn(null);
|
||||
if (on) {
|
||||
await startDownloads();
|
||||
} else {
|
||||
await killDownloads();
|
||||
}
|
||||
await fetchDownloaders();
|
||||
}
|
||||
|
||||
return {on, setOn, downloaders, setDownloads: localSetDownloads};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,4 @@ COPY docker/archive /app
|
|||
# Install Readability too
|
||||
RUN npm install -g 'git+https://github.com/pirate/readability-extractor'
|
||||
|
||||
# Block ads using /etc/hosts
|
||||
#RUN wget https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts -O /etc/hosts
|
||||
|
||||
ENTRYPOINT [ "python3", "/app/main.py"]
|
||||
|
|
|
|||
|
|
@ -19,4 +19,4 @@ SQLAlchemy==1.3.22
|
|||
srt==3.5.1
|
||||
websockets==10.1
|
||||
webvtt-py==0.4.6
|
||||
yt-dlp==2022.1.21
|
||||
yt-dlp==2022.2.4
|
||||
|
|
|
|||
67
wrolpi/admin.py
Normal file
67
wrolpi/admin.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import enum
|
||||
import pathlib
|
||||
import subprocess
|
||||
|
||||
from wrolpi.common import logger
|
||||
from wrolpi.vars import PYTEST
|
||||
|
||||
logger = logger.getChild(__name__)
|
||||
|
||||
# Location on an RPi
|
||||
SUDO_BIN = pathlib.Path('/usr/bin/sudo')
|
||||
if not SUDO_BIN.is_file() and not PYTEST:
|
||||
logger.error('COULD NOT FIND sudo!!!')
|
||||
NMCLI_BIN = pathlib.Path('/usr/bin/nmcli')
|
||||
if not NMCLI_BIN.is_file() and not PYTEST:
|
||||
logger.error('COULD NOT FIND nmcli!!!')
|
||||
|
||||
|
||||
class HotspotStatus(enum.Enum):
|
||||
disconnected = enum.auto() # Radio is on, but Hotspot is not connected.
|
||||
unavailable = enum.auto() # Radio is off.
|
||||
connected = enum.auto() # Radio is on, Hotspot is on.
|
||||
|
||||
|
||||
def hotspot_status() -> HotspotStatus:
|
||||
cmd = (NMCLI_BIN,)
|
||||
output = subprocess.check_output(cmd).decode().strip()
|
||||
|
||||
for line in output.splitlines():
|
||||
if line.startswith('wlan0: connected'):
|
||||
return HotspotStatus.connected
|
||||
elif line.startswith('wlan0: disconnected'):
|
||||
return HotspotStatus.disconnected
|
||||
elif line.startswith('wlan0: unavailable'):
|
||||
return HotspotStatus.unavailable
|
||||
|
||||
raise Exception(f'Unknown status! {output=}')
|
||||
|
||||
|
||||
def enable_hotspot():
|
||||
"""
|
||||
Turn the wlan0 interface into a hotspot. If wlan0 is already running, replace that with a hotspot.
|
||||
"""
|
||||
status = hotspot_status()
|
||||
logger.warning(f'Hotspot status: {status}')
|
||||
|
||||
if status == HotspotStatus.connected:
|
||||
disable_hotspot()
|
||||
return enable_hotspot()
|
||||
elif status == HotspotStatus.disconnected:
|
||||
# Radio is on, but not connected. Good, turn it into a hotspot.
|
||||
cmd = (SUDO_BIN, NMCLI_BIN, 'device', 'wifi', 'hotspot', 'ifname', 'wlan0',
|
||||
'ssid', 'WROLPi', 'password', 'wrolpi hotspot')
|
||||
subprocess.check_call(cmd)
|
||||
return True
|
||||
elif status == HotspotStatus.unavailable:
|
||||
# Radio is not on, turn it on.
|
||||
cmd = (SUDO_BIN, NMCLI_BIN, 'radio', 'wifi', 'on')
|
||||
subprocess.check_call(cmd)
|
||||
return enable_hotspot()
|
||||
|
||||
|
||||
def disable_hotspot():
|
||||
"""Turn off the wlan0 interface."""
|
||||
cmd = (SUDO_BIN, NMCLI_BIN, 'radio', 'wifi', 'off')
|
||||
subprocess.check_call(cmd)
|
||||
return True
|
||||
|
|
@ -27,8 +27,9 @@ from sqlalchemy import types
|
|||
from sqlalchemy.ext.compiler import compiles
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
||||
from wrolpi.errors import WROLModeEnabled
|
||||
from wrolpi.vars import CONFIG_PATH, EXAMPLE_CONFIG_PATH, PUBLIC_HOST, PUBLIC_PORT, PROJECT_DIR, PYTEST, MODULES_DIR
|
||||
from wrolpi.errors import WROLModeEnabled, NativeOnly
|
||||
from wrolpi.vars import CONFIG_PATH, EXAMPLE_CONFIG_PATH, PUBLIC_HOST, PUBLIC_PORT, PROJECT_DIR, PYTEST, MODULES_DIR, \
|
||||
DOCKERIZED
|
||||
|
||||
logger = logging.getLogger()
|
||||
ch = logging.StreamHandler()
|
||||
|
|
@ -655,3 +656,14 @@ INVALID_FILE_CHARS = re.compile(r'[/<>:\|"\\\?\*]')
|
|||
def escape_file_name(name: str) -> str:
|
||||
"""Replace any invalid characters in a file name with "_"."""
|
||||
return INVALID_FILE_CHARS.sub('', name)
|
||||
|
||||
|
||||
def native_only(func: callable):
|
||||
"""Wraps a function. Raises NativeOnly if run while Dockerized."""
|
||||
@wraps(func)
|
||||
def wrapped(*a, **kw):
|
||||
if DOCKERIZED:
|
||||
raise NativeOnly('Only supported on a Raspberry Pi')
|
||||
return func(*a, **kw)
|
||||
|
||||
return wrapped
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ class InvalidFile(APIError):
|
|||
pass
|
||||
|
||||
|
||||
class NativeOnly(APIError):
|
||||
pass
|
||||
|
||||
|
||||
error_codes = iter(range(1, 1000))
|
||||
|
||||
API_ERRORS = {
|
||||
|
|
@ -307,5 +311,10 @@ API_ERRORS = {
|
|||
'code': next(error_codes),
|
||||
'message': 'File does not exist or is a directory',
|
||||
'status': HTTPStatus.BAD_REQUEST,
|
||||
}
|
||||
},
|
||||
NativeOnly: {
|
||||
'code': next(error_codes),
|
||||
'message': 'This functionality is only supported outside a docker container',
|
||||
'status': HTTPStatus.BAD_REQUEST,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ from sanic.response import HTTPResponse
|
|||
from sanic_ext import validate
|
||||
from sanic_ext.extensions.openapi import openapi
|
||||
|
||||
from wrolpi import admin
|
||||
from wrolpi.admin import HotspotStatus
|
||||
from wrolpi.common import set_sanic_url_parts, logger, get_config, wrol_mode_enabled, save_settings_config, \
|
||||
Base, get_media_directory, wrol_mode_check
|
||||
Base, get_media_directory, wrol_mode_check, native_only
|
||||
from wrolpi.dates import set_timezone
|
||||
from wrolpi.downloader import download_manager
|
||||
from wrolpi.errors import WROLModeEnabled, InvalidTimezone, API_ERRORS, APIError, ValidationError
|
||||
|
|
@ -210,6 +212,38 @@ async def get_downloaders(_: Request):
|
|||
return json_response(ret)
|
||||
|
||||
|
||||
@root_api.get('/hotspot')
|
||||
@native_only
|
||||
@openapi.description('Get the status of the hotspot')
|
||||
async def hotspot(_: Request):
|
||||
status = admin.hotspot_status()
|
||||
# Get the name of the enum.
|
||||
status = status.name
|
||||
|
||||
ret = dict(status=status)
|
||||
return json_response(ret)
|
||||
|
||||
|
||||
@root_api.post('/hotspot/on')
|
||||
@openapi.description('Turn on the hotspot')
|
||||
@native_only
|
||||
async def hotspot_on(_: Request):
|
||||
result = admin.enable_hotspot()
|
||||
if result:
|
||||
return response.empty()
|
||||
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@root_api.post('/hotspot/off')
|
||||
@openapi.description('Turn off the hotspot')
|
||||
@native_only
|
||||
async def hotspot_off(_: Request):
|
||||
result = admin.disable_hotspot()
|
||||
if result:
|
||||
return response.empty()
|
||||
return response.empty(HTTPStatus.INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
class CustomJSONEncoder(json.JSONEncoder):
|
||||
|
||||
def default(self, obj):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
@ -52,7 +52,7 @@ class EventsResponse:
|
|||
@dataclass
|
||||
class DownloadRequest:
|
||||
urls: str
|
||||
downloader: str
|
||||
downloader: Optional[str] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
63
wrolpi/test/test_admin.py
Normal file
63
wrolpi/test/test_admin.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
from pathlib import PosixPath
|
||||
from unittest import mock
|
||||
from unittest.mock import call
|
||||
|
||||
from wrolpi import admin
|
||||
from wrolpi.admin import HotspotStatus
|
||||
|
||||
|
||||
def test_hotspot_status():
|
||||
with mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
|
||||
mock_subprocess.check_output.return_value = b'wlan0: unavailable'
|
||||
assert admin.hotspot_status() == HotspotStatus.unavailable
|
||||
mock_subprocess.check_output.return_value = b'wlan0: disconnected'
|
||||
assert admin.hotspot_status() == HotspotStatus.disconnected
|
||||
mock_subprocess.check_output.return_value = b'wlan0: connected'
|
||||
assert admin.hotspot_status() == HotspotStatus.connected
|
||||
|
||||
|
||||
def test_enable_hotspot_disconnected():
|
||||
"""Enable hotspot with wifi on."""
|
||||
with mock.patch('wrolpi.admin.hotspot_status') as mock_hotspot_status, \
|
||||
mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
|
||||
mock_hotspot_status.return_value = HotspotStatus.disconnected
|
||||
mock_subprocess.check_output.return_value = 0
|
||||
assert admin.enable_hotspot() is True
|
||||
mock_subprocess.check_call.assert_called_once_with(
|
||||
(PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'device', 'wifi', 'hotspot', 'ifname', 'wlan0',
|
||||
'ssid', 'WROLPi', 'password', 'wrolpi hotspot'))
|
||||
|
||||
|
||||
def test_enable_hotspot_unavailable():
|
||||
"""Enable hotspot with wifi off."""
|
||||
with mock.patch('wrolpi.admin.hotspot_status') as mock_hotspot_status, \
|
||||
mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
|
||||
mock_hotspot_status.side_effect = [HotspotStatus.unavailable, HotspotStatus.disconnected]
|
||||
mock_subprocess.check_output.return_value = 0
|
||||
assert admin.enable_hotspot() is True
|
||||
mock_subprocess.check_call.assert_has_calls(
|
||||
[
|
||||
call((PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'radio', 'wifi', 'on')),
|
||||
call((PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'device', 'wifi', 'hotspot', 'ifname',
|
||||
'wlan0', 'ssid', 'WROLPi', 'password', 'wrolpi hotspot'))]
|
||||
)
|
||||
|
||||
|
||||
def test_enable_hotspot_connected():
|
||||
"""Enable hotspot with the hotspot alaready enabled."""
|
||||
with mock.patch('wrolpi.admin.hotspot_status') as mock_hotspot_status, \
|
||||
mock.patch('wrolpi.admin.subprocess') as mock_subprocess:
|
||||
mock_hotspot_status.side_effect = [
|
||||
HotspotStatus.connected,
|
||||
HotspotStatus.unavailable,
|
||||
HotspotStatus.disconnected,
|
||||
]
|
||||
mock_subprocess.check_output.return_value = 0
|
||||
assert admin.enable_hotspot() is True
|
||||
mock_subprocess.check_call.assert_has_calls(
|
||||
[
|
||||
call((PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'radio', 'wifi', 'off')),
|
||||
call((PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'radio', 'wifi', 'on')),
|
||||
call((PosixPath('/usr/bin/sudo'), PosixPath('/usr/bin/nmcli'), 'device', 'wifi', 'hotspot', 'ifname',
|
||||
'wlan0', 'ssid', 'WROLPi', 'password', 'wrolpi hotspot'))]
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue