Compare commits

..
Author SHA1 Message Date
forust 94ff1d12e3 fix(edu-master): bound session refreshes and expire stale cookies
renovate-ci / validate-renovate (push) Skipped
ci / lint-compose (push) Successful in 14s
ci / lint-actionlint (push) Successful in 8s
ci / lint-shellcheck (push) Successful in 9s
ci / lint-prettier (push) Successful in 18s
ci / lint-ruff (push) Successful in 10s
ci / lint-yaml (push) Successful in 13s
ci / lint-dockerfiles (push) Successful in 6s
ci / validate (push) Successful in 6s
ci / build (push) Skipped
ci / lint-compose (pull_request) Successful in 9s
ci / lint-actionlint (pull_request) Successful in 4s
ci / lint-shellcheck (pull_request) Successful in 7s
ci / lint-prettier (pull_request) Successful in 16s
ci / lint-ruff (pull_request) Successful in 9s
ci / lint-yaml (pull_request) Successful in 10s
ci / lint-dockerfiles (pull_request) Successful in 4s
ci / validate (pull_request) Successful in 6s
ci / build (pull_request) Skipped
renovate-ci / validate-renovate (pull_request) Successful in 8s
2026-10-06 15:58:44 +02:00
forust cc9c3dea88 feat(traefik): expose insecure API on 8080 for Homarr integration
ci / lint-compose (push) Successful in 10s
ci / lint-actionlint (push) Successful in 8s
ci / lint-shellcheck (push) Successful in 8s
ci / lint-prettier (push) Successful in 16s
ci / lint-ruff (push) Successful in 6s
ci / lint-yaml (push) Successful in 9s
ci / lint-dockerfiles (push) Successful in 5s
ci / validate (push) Successful in 7s
renovate-ci / validate-renovate (push) Successful in 9s
ci / build (push) Successful in 38s
ClusterIP access to api@internal from Homarr pod. LAN-reachable on 192.168.80.2:8080, accepted.
2026-10-06 11:27:38 +02:00
forust 815cd85b9a feat(homarr): deploy dashboard to k8s on home subdomain
Local-only IngressRoute (home.workstation.internal, home.gigaforust.internal), prod commented out. Compose stack for test stand.
2026-10-06 11:27:35 +02:00
forust 9021eddbc3 chore(deps): pin streaming images, isolate python and floating tags
ci / lint-compose (push) Successful in 8s
ci / lint-actionlint (push) Successful in 4s
ci / lint-shellcheck (push) Successful in 8s
ci / lint-prettier (push) Successful in 13s
ci / lint-ruff (push) Successful in 7s
ci / lint-yaml (push) Successful in 9s
ci / lint-dockerfiles (push) Successful in 5s
ci / validate (push) Successful in 7s
renovate-ci / validate-renovate (push) Successful in 9s
ci / build (push) Successful in 19s
Python Y-bumps arrived as minor 3.11->3.14 and floating tags (:latest/:beta) were automerged blindly. Pin the linuxserver stack to digest-verified tags and keep python plus rolling images in manual review groups.
2026-10-05 23:53:38 +02:00
forust 2adf17c307 Merge pull request 'chore(deps): update all patch updates' (#76) from renovate/all-patch into main
ci / lint-compose (push) Successful in 8s
ci / lint-actionlint (push) Successful in 5s
ci / lint-shellcheck (push) Successful in 8s
ci / lint-prettier (push) Successful in 12s
ci / lint-ruff (push) Successful in 6s
ci / lint-yaml (push) Successful in 9s
ci / lint-dockerfiles (push) Successful in 5s
ci / validate (push) Successful in 6s
renovate-ci / validate-renovate (push) Successful in 9s
ci / build (push) Successful in 17s
Reviewed-on: #76
2026-10-05 21:41:00 +00:00
renovate-bot 1add5b5cd7 chore(deps): update all patch updates 2026-10-05 21:41:00 +00:00
forust 34f10211ab Merge pull request 'chore(deps): update renovate/renovate docker tag to v44.136.0' (#79) from renovate/renovate-self-update into main
ci / lint-compose (push) Successful in 9s
ci / lint-actionlint (push) Successful in 5s
ci / lint-shellcheck (push) Successful in 7s
ci / lint-prettier (push) Successful in 13s
ci / lint-yaml (push) Successful in 10s
ci / validate (push) Successful in 7s
renovate-ci / validate-renovate (push) Successful in 9s
ci / lint-ruff (push) Successful in 6s
ci / lint-dockerfiles (push) Successful in 5s
ci / build (push) Successful in 18s
Reviewed-on: #79
2026-10-05 21:39:21 +00:00
renovate-bot 02447f2946 chore(deps): update renovate/renovate docker tag to v44.136.0
renovate-ci / validate-renovate (push) Skipped
ci / lint-compose (push) Successful in 11s
ci / lint-actionlint (push) Successful in 5s
ci / lint-shellcheck (push) Successful in 7s
ci / lint-ruff (push) Successful in 5s
ci / validate (push) Successful in 9s
ci / lint-prettier (push) Successful in 13s
ci / lint-yaml (push) Successful in 10s
ci / lint-dockerfiles (push) Successful in 5s
ci / build (push) Skipped
ci / lint-compose (pull_request) Successful in 8s
ci / lint-actionlint (pull_request) Successful in 6s
ci / lint-shellcheck (pull_request) Successful in 12s
ci / lint-prettier (pull_request) Successful in 14s
ci / lint-ruff (pull_request) Successful in 6s
ci / lint-yaml (pull_request) Successful in 9s
ci / lint-dockerfiles (pull_request) Successful in 5s
ci / validate (pull_request) Successful in 6s
ci / build (pull_request) Skipped
renovate-ci / validate-renovate (pull_request) Successful in 1m18s
2026-10-05 16:19:11 +00:00
22 changed files with 443 additions and 58 deletions

No files matched your search

+1
View File
@@ -141,6 +141,7 @@ jobs:
export PATH="$tools_dir:$PATH"
ruff check .
ruff format --check .
python3 -m unittest discover -s tests -v
lint-yaml:
runs-on: [self-hosted, linux, arch, homelab]
+7 -6
View File
@@ -1,10 +1,11 @@
EDU_LOGIN=your_edu_login_here
EDU_PASSWORD=your_edu_password_here
EDU_URL_LOGIN=https://edu.edu.vn.ua/user/login
EDU_URL_VERIFY=https://edu.edu.vn.ua/course/userlist
PHPSESSID_INTERVAL=10
KEEPER_LOGIN=your_edu_login_here
KEEPER_PASSWORD=your_edu_password_here
EDU_URL_BASE=https://edu.edu.vn.ua
EDU_URL_LOGIN=/user/login
EDU_URL_COURSES=/course/userlist
KEEPER_INTERVAL=10
USER_AGENT="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36"
WEBINAR_URL=https://edu.edu.vn.ua/webinar/useractive
EDU_URL_WEBINAR=/webinar/useractive
WEBINAR_CHECK_INTERVAL=60
REDIS_HOST=redis
REDIS_PORT=6379
+29 -40
View File
@@ -48,17 +48,43 @@ def touch_success_file():
logger.error(f'Failed to touch success file: {e}')
def refresh_session(session, redis_client):
"""Publish a verified cookie with a lifetime tied to the refresh interval."""
login_response = session.post(
URL_LOGIN, data={'login': LOGIN, 'password': PASSWORD}, allow_redirects=True, timeout=(10, 30)
)
login_response.raise_for_status()
verify_response = session.get(URL_VERIFY, allow_redirects=False, timeout=(10, 30))
if verify_response.status_code != 200:
logger.warning('Session verification failed (HTTP %s)', verify_response.status_code)
return False
phpsessid = session.cookies.get('PHPSESSID')
if not phpsessid:
logger.warning('Verified response did not provide a PHPSESSID cookie')
return False
redis_client.set('EDU_PHPSESSID', phpsessid, ex=INTERVAL * 120)
touch_success_file()
logger.info('Verified session saved to Redis')
return True
def main():
if not LOGIN or not PASSWORD:
raise ValueError('KEEPER_LOGIN and KEEPER_PASSWORD must be set')
if INTERVAL <= 0:
raise ValueError('KEEPER_INTERVAL must be a positive number of minutes')
logger.info('Starting Session Keeper Bot')
# Connect to Redis
try:
redis_client = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True)
redis_client = redis.Redis(
host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, socket_connect_timeout=5, socket_timeout=5
)
redis_client.ping()
logger.info(f'Connected to Redis at {REDIS_HOST}:{REDIS_PORT}')
except Exception as e:
logger.error(f'Failed to connect to Redis: {e}')
return
raise
session = requests.Session()
@@ -83,44 +109,7 @@ def main():
while True:
try:
logger.info('Attempting login...')
# Login payload
payload = {'login': LOGIN, 'password': PASSWORD}
# Perform Login
# Note: The user request shows a POST to /user/login with form data
# We need to make sure we handle the PHPSESSID correctly.
# If we already have a PHPSESSID, requests will send it.
login_response = session.post(URL_LOGIN, data=payload, allow_redirects=True)
logger.info(f'Login Response Status: {login_response.status_code}')
logger.info(f'Cookies after login: {session.cookies.get_dict()}')
# Verify Session
logger.info('Verifying session...')
verify_response = session.get(URL_VERIFY, allow_redirects=False)
logger.info(f'Verify Response Status: {verify_response.status_code}')
if verify_response.status_code == 200:
logger.info('Session verification SUCCESS (200 OK).')
touch_success_file()
# Save PHPSESSID to Redis
phpsessid = session.cookies.get('PHPSESSID')
if phpsessid:
try:
redis_client.set('EDU_PHPSESSID', phpsessid)
logger.info(f'Saved PHPSESSID to Redis: {phpsessid}')
except Exception as e:
logger.error(f'Failed to save PHPSESSID to Redis: {e}')
elif verify_response.status_code == 302:
logger.warning('Session verification FAILED (302 Redirect). Session might be invalid.')
else:
logger.warning(f'Session verification returned unexpected status: {verify_response.status_code}')
refresh_session(session, redis_client)
except Exception as e:
logger.error(f'An error occurred: {e}')
+4
View File
@@ -0,0 +1,4 @@
SECRET_ENCRYPTION_KEY="REPLACE_ME"
TZ="Europe/Bratislava"
PUID="1000"
PGID="1000"
+38
View File
@@ -0,0 +1,38 @@
services:
homarr:
container_name: homarr
image: ghcr.io/homarr-labs/homarr:v2.1.2
restart: unless-stopped
volumes:
- ./appdata:/appdata
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./kubeconfig:/app/config/kubeconfig:ro
env_file: .env
ports:
- 80:7575
- 81:3000
environment:
- TZ=${TZ:-Europe/Bratislava}
- TURBO_TELEMETRY_DISABLED=1
- KUBECONFIG=/app/config/kubeconfig
labels:
- "traefik.enable=true"
- "traefik.http.services.homarr.loadbalancer.server.port=7575"
# Prod Router
- "traefik.http.routers.homarr.rule=Host(`homarr.forust.xyz`)"
- "traefik.http.routers.homarr.entrypoints=websecure"
- "traefik.http.routers.homarr.tls.certresolver=letsencrypt"
# Local Router
- "traefik.http.routers.homarr-local.rule=Host(`homarr.workstation.internal`)"
- "traefik.http.routers.homarr-local.entrypoints=websecure"
- "traefik.http.routers.homarr-local.tls=true"
# Dev Router
- "traefik.http.routers.homarr-dev.rule=Host(`homarr.gigaforust.internal`)"
- "traefik.http.routers.homarr-dev.entrypoints=websecure"
- "traefik.http.routers.homarr-dev.tls=true"
networks:
- proxy
networks:
proxy:
external: true
+28
View File
@@ -0,0 +1,28 @@
# apiVersion: cert-manager.io/v1
# kind: Certificate
# metadata:
# name: home-prod-tls
# namespace: homarr
# spec:
# secretName: home-prod-tls
# dnsNames:
# - home.forust.xyz
# issuerRef:
# name: letsencrypt-prod
# kind: ClusterIssuer
# ---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: internal-wildcard-tls
namespace: homarr
spec:
secretName: internal-wildcard-tls
dnsNames:
- "*.workstation.internal"
- "*.gigaforust.internal"
- workstation.internal
- gigaforust.internal
issuerRef:
name: internal-ca
kind: ClusterIssuer
+9
View File
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: homarr-config
namespace: homarr
data:
TZ: "Europe/Bratislava"
TURBO_TELEMETRY_DISABLED: "1"
ENABLE_KUBERNETES: "true"
+81
View File
@@ -0,0 +1,81 @@
apiVersion: v1
kind: Service
metadata:
name: homarr-service
namespace: homarr
spec:
selector:
app: homarr
ports:
- port: 7575
targetPort: 7575
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: homarr-deployment
namespace: homarr
spec:
replicas: 1
selector:
matchLabels:
app: homarr
strategy:
type: Recreate
template:
metadata:
labels:
app: homarr
spec:
serviceAccountName: homarr
containers:
- name: homarr
image: ghcr.io/homarr-labs/homarr:v2.1.2
envFrom:
- configMapRef:
name: homarr-config
- secretRef:
name: homarr-secrets
ports:
- containerPort: 7575
readinessProbe:
httpGet:
path: /
port: 7575
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 6
livenessProbe:
httpGet:
path: /
port: 7575
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 3
volumeMounts:
- name: homarr-data
mountPath: /appdata
resources:
requests:
cpu: "250m"
memory: "350Mi"
limits:
cpu: "500m"
memory: "700Mi"
volumes:
- name: homarr-data
persistentVolumeClaim:
claimName: homarr-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: homarr-pvc
namespace: homarr
spec:
resources:
requests:
storage: 2Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
+33
View File
@@ -0,0 +1,33 @@
# apiVersion: traefik.io/v1alpha1
# kind: IngressRoute
# metadata:
# name: homarr-prod
# namespace: homarr
# spec:
# entryPoints:
# - websecure
# routes:
# - match: Host(`home.forust.xyz`)
# kind: Rule
# services:
# - name: homarr-service
# port: 7575
# tls:
# secretName: home-prod-tls
# ---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: homarr-local
namespace: homarr
spec:
entryPoints:
- websecure
routes:
- match: Host(`home.workstation.internal`) || Host(`home.gigaforust.internal`)
kind: Rule
services:
- name: homarr-service
port: 7575
tls:
secretName: internal-wildcard-tls
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: homarr
+58
View File
@@ -0,0 +1,58 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: homarr
namespace: homarr
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: homarr-readonly
rules:
- apiGroups: [""]
resources:
- pods
- services
- endpoints
- namespaces
- nodes
- configmaps
- persistentvolumeclaims
- events
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources:
- deployments
- statefulsets
- daemonsets
- replicasets
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources:
- ingresses
verbs: ["get", "list", "watch"]
- apiGroups: ["traefik.io"]
resources:
- ingressroutes
- ingressroutetcps
- ingressrouteudps
- middlewares
verbs: ["get", "list", "watch"]
- apiGroups: ["metrics.k8s.io"]
resources:
- pods
- nodes
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: homarr-readonly
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: homarr-readonly
subjects:
- kind: ServiceAccount
name: homarr
namespace: homarr
+9
View File
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Secret
metadata:
name: homarr-secrets
namespace: homarr
type: Opaque
stringData:
# openssl rand -hex 32
SECRET_ENCRYPTION_KEY: "REPLACE_ME"
+1 -1
View File
@@ -1,6 +1,6 @@
services:
n8n:
image: docker.n8n.io/n8nio/n8n:2.42.2
image: docker.n8n.io/n8nio/n8n:2.42.3
container_name: n8n
restart: unless-stopped
environment:
+1 -1
View File
@@ -29,7 +29,7 @@ spec:
spec:
containers:
- name: n8n
image: docker.n8n.io/n8nio/n8n:2.42.2
image: docker.n8n.io/n8nio/n8n:2.42.3
envFrom:
- configMapRef:
name: n8n-config
+28
View File
@@ -218,6 +218,34 @@ data:
"matchUpdateTypes": ["patch"],
"groupName": "all patch updates",
"groupSlug": "all-patch"
},
{
"description": "Python Y-bumps break compat (3.11->3.12->3.13->3.14) - keep the base image out of the shared minor/patch groups, review every bump separately. Placed last so its groupName wins.",
"matchDatasources": ["docker"],
"matchPackageNames": ["python"],
"groupName": "python base image",
"groupSlug": "python",
"automerge": false
},
{
"description": "Rolling/floating tags (streaming stack, nextcloud beta, kubectl latest) - never automerge, every bump is a manual review. Placed last so automerge:false wins over the shared digest/patch rule.",
"matchDatasources": ["docker"],
"matchPackageNames": [
"lscr.io/linuxserver/jellyfin",
"lscr.io/linuxserver/qbittorrent",
"lscr.io/linuxserver/sonarr",
"lscr.io/linuxserver/radarr",
"lscr.io/linuxserver/prowlarr",
"lscr.io/linuxserver/bazarr",
"ghcr.io/seerr-team/seerr",
"fallenbagel/jellyseerr",
"ghcr.io/lampac-nextgen/lampac",
"ghcr.io/nextcloud-releases/all-in-one",
"alpine/kubectl"
],
"groupName": "floating images - manual",
"groupSlug": "floating-manual",
"automerge": false
}
]
}
+1 -1
View File
@@ -19,7 +19,7 @@ spec:
restartPolicy: Never
containers:
- name: renovate
image: renovate/renovate:44.132.6
image: renovate/renovate:44.136.0
env:
- name: RENOVATE_PLATFORM
value: gitea
+28
View File
@@ -207,6 +207,34 @@
"matchUpdateTypes": ["patch"],
"groupName": "all patch updates",
"groupSlug": "all-patch"
},
{
"description": "Python Y-bumps break compat (3.11->3.12->3.13->3.14) - keep the base image out of the shared minor/patch groups, review every bump separately. Placed last so its groupName wins.",
"matchDatasources": ["docker"],
"matchPackageNames": ["python"],
"groupName": "python base image",
"groupSlug": "python",
"automerge": false
},
{
"description": "Rolling/floating tags (streaming stack, nextcloud beta, kubectl latest) - never automerge, every bump is a manual review. Placed last so automerge:false wins over the shared digest/patch rule.",
"matchDatasources": ["docker"],
"matchPackageNames": [
"lscr.io/linuxserver/jellyfin",
"lscr.io/linuxserver/qbittorrent",
"lscr.io/linuxserver/sonarr",
"lscr.io/linuxserver/radarr",
"lscr.io/linuxserver/prowlarr",
"lscr.io/linuxserver/bazarr",
"ghcr.io/seerr-team/seerr",
"fallenbagel/jellyseerr",
"ghcr.io/lampac-nextgen/lampac",
"ghcr.io/nextcloud-releases/all-in-one",
"alpine/kubectl"
],
"groupName": "floating images - manual",
"groupSlug": "floating-manual",
"automerge": false
}
]
}
+6 -6
View File
@@ -1,6 +1,6 @@
services:
jellyfin:
image: lscr.io/linuxserver/jellyfin:latest
image: lscr.io/linuxserver/jellyfin:version-12.1ubu2604
container_name: jellyfin
restart: unless-stopped
environment:
@@ -19,7 +19,7 @@ services:
- streaming
qbittorrent:
image: lscr.io/linuxserver/qbittorrent:latest
image: lscr.io/linuxserver/qbittorrent:5.2.4
container_name: qbittorrent
restart: unless-stopped
environment:
@@ -38,7 +38,7 @@ services:
- streaming
sonarr:
image: lscr.io/linuxserver/sonarr:latest
image: lscr.io/linuxserver/sonarr:4.0.20
container_name: sonarr
restart: unless-stopped
environment:
@@ -55,7 +55,7 @@ services:
- streaming
radarr:
image: lscr.io/linuxserver/radarr:latest
image: lscr.io/linuxserver/radarr:6.4.4
container_name: radarr
restart: unless-stopped
environment:
@@ -72,7 +72,7 @@ services:
- streaming
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
image: lscr.io/linuxserver/prowlarr:2.6.5
container_name: prowlarr
restart: unless-stopped
environment:
@@ -100,7 +100,7 @@ services:
- streaming
bazarr:
image: lscr.io/linuxserver/bazarr:latest
image: lscr.io/linuxserver/bazarr:1.6.2
container_name: bazarr
restart: unless-stopped
environment:
+1 -1
View File
@@ -1,6 +1,6 @@
services:
termix:
image: ghcr.io/lukegus/termix:2.9.0
image: ghcr.io/lukegus/termix:2.9.1
container_name: termix
restart: unless-stopped
# ports:
+1 -1
View File
@@ -29,7 +29,7 @@ spec:
spec:
containers:
- name: termix
image: ghcr.io/lukegus/termix:2.9.0
image: ghcr.io/lukegus/termix:2.9.1
envFrom:
- configMapRef:
name: termix-config
+74
View File
@@ -0,0 +1,74 @@
"""Session publication checks without Redis, the EDU website, or credentials."""
import importlib.util
import sys
import unittest
from pathlib import Path
from unittest.mock import Mock, patch
SCRIPT = Path(__file__).resolve().parents[1] / 'edu_master/phpsessid-bot/bot.py'
spec = importlib.util.spec_from_file_location('session_keeper', SCRIPT)
bot = importlib.util.module_from_spec(spec)
with patch.dict(sys.modules, {'redis': Mock(), 'requests': Mock()}):
spec.loader.exec_module(bot)
class SessionKeeperTests(unittest.TestCase):
def setUp(self):
self.session = Mock()
self.session.get.return_value.status_code = 200
self.session.cookies.get.return_value = 'test-cookie'
self.redis = Mock()
self.touch = patch.object(bot, 'touch_success_file').start()
self.addCleanup(patch.stopall)
def test_verified_cookie_expires_and_is_not_logged(self):
with self.assertLogs(bot.logger, level='INFO') as logs:
self.assertTrue(bot.refresh_session(self.session, self.redis))
self.redis.set.assert_called_once_with('EDU_PHPSESSID', 'test-cookie', ex=bot.INTERVAL * 120)
self.touch.assert_called_once()
self.assertNotIn('test-cookie', '\n'.join(logs.output))
self.assertEqual(self.session.post.call_args.kwargs['timeout'], (10, 30))
self.assertEqual(self.session.get.call_args.kwargs['timeout'], (10, 30))
def test_redirect_does_not_publish(self):
self.session.get.return_value.status_code = 302
self.assertFalse(bot.refresh_session(self.session, self.redis))
self.redis.set.assert_not_called()
self.touch.assert_not_called()
def test_missing_cookie_does_not_mark_success(self):
self.session.cookies.get.return_value = None
self.assertFalse(bot.refresh_session(self.session, self.redis))
self.redis.set.assert_not_called()
self.touch.assert_not_called()
def test_redis_failure_does_not_mark_success(self):
self.redis.set.side_effect = OSError('redis unavailable')
with self.assertRaises(OSError):
bot.refresh_session(self.session, self.redis)
self.touch.assert_not_called()
def test_http_timeout_does_not_publish(self):
self.session.post.side_effect = TimeoutError('EDU unavailable')
with self.assertRaises(TimeoutError):
bot.refresh_session(self.session, self.redis)
self.redis.set.assert_not_called()
self.touch.assert_not_called()
def test_missing_credentials_fail_before_network_access(self):
with patch.object(bot, 'LOGIN', None), self.assertRaises(ValueError):
bot.main()
def test_nonpositive_interval_fails_before_network_access(self):
with (
patch.object(bot, 'LOGIN', 'test'),
patch.object(bot, 'PASSWORD', 'test'),
patch.object(bot, 'INTERVAL', 0),
self.assertRaises(ValueError),
):
bot.main()
if __name__ == '__main__':
unittest.main()
+1 -1
View File
@@ -94,7 +94,7 @@ ports:
exposedPort: 8080
protocol: TCP
expose:
default: false
default: true
http:
aliasHeadersStrategy: delete
ssh: