mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 19:02:31 +00:00
i18n: localize stopped scheduled update reason
This commit is contained in:
@@ -35,6 +35,23 @@ import DOMPurify from "dompurify"
|
|||||||
import { marked } from "marked"
|
import { marked } from "marked"
|
||||||
import { getCountFormKey, useI18n, useT } from "@/lib/i18n/provider"
|
import { getCountFormKey, useI18n, useT } from "@/lib/i18n/provider"
|
||||||
|
|
||||||
|
// A schedule record predating the structured reason code still contains one
|
||||||
|
// of these exact diagnostic messages. Recognise only those known historical
|
||||||
|
// values so an already-recorded safe skip localizes after an upgrade, while
|
||||||
|
// unrelated freeform diagnostics remain verbatim for troubleshooting.
|
||||||
|
const LEGACY_STOPPED_SCHEDULE_REASONS = new Set([
|
||||||
|
"container is stopped; scheduled update was skipped",
|
||||||
|
"container was stopped before scheduled update could begin",
|
||||||
|
])
|
||||||
|
|
||||||
|
const scheduleReasonCodeFromPersisted = (reasonCode: unknown, reason: unknown): string | null => {
|
||||||
|
if (typeof reasonCode === "string" && reasonCode) return reasonCode
|
||||||
|
if (typeof reason === "string" && LEGACY_STOPPED_SCHEDULE_REASONS.has(reason.toLowerCase())) {
|
||||||
|
return "stopped_container"
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Sent by /api/vms only for LXC rows, only when the user has enabled
|
// Sent by /api/vms only for LXC rows, only when the user has enabled
|
||||||
// `lxc_updates_available` notifications. The Monitor populates this
|
// `lxc_updates_available` notifications. The Monitor populates this
|
||||||
// from managed_installs registry → frontend uses it to render the
|
// from managed_installs registry → frontend uses it to render the
|
||||||
@@ -2077,6 +2094,7 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
const [scheduleLastRunAt, setScheduleLastRunAt] = useState<string | null>(null)
|
const [scheduleLastRunAt, setScheduleLastRunAt] = useState<string | null>(null)
|
||||||
const [scheduleLastRunStatus, setScheduleLastRunStatus] = useState<string | null>(null)
|
const [scheduleLastRunStatus, setScheduleLastRunStatus] = useState<string | null>(null)
|
||||||
const [scheduleLastRunReason, setScheduleLastRunReason] = useState<string | null>(null)
|
const [scheduleLastRunReason, setScheduleLastRunReason] = useState<string | null>(null)
|
||||||
|
const [scheduleLastRunReasonCode, setScheduleLastRunReasonCode] = useState<string | null>(null)
|
||||||
const [scheduleLastRunLog, setScheduleLastRunLog] = useState<string | null>(null)
|
const [scheduleLastRunLog, setScheduleLastRunLog] = useState<string | null>(null)
|
||||||
const [scheduleLastRunRebootRequired, setScheduleLastRunRebootRequired] = useState(false)
|
const [scheduleLastRunRebootRequired, setScheduleLastRunRebootRequired] = useState(false)
|
||||||
const [scheduleLastRunRebootPackages, setScheduleLastRunRebootPackages] = useState<string[]>([])
|
const [scheduleLastRunRebootPackages, setScheduleLastRunRebootPackages] = useState<string[]>([])
|
||||||
@@ -2153,6 +2171,7 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
setScheduleLastRunAt(s.last_run_at || null)
|
setScheduleLastRunAt(s.last_run_at || null)
|
||||||
setScheduleLastRunStatus(s.last_run_status || null)
|
setScheduleLastRunStatus(s.last_run_status || null)
|
||||||
setScheduleLastRunReason(s.last_run_reason || null)
|
setScheduleLastRunReason(s.last_run_reason || null)
|
||||||
|
setScheduleLastRunReasonCode(scheduleReasonCodeFromPersisted(s.last_run_reason_code, s.last_run_reason))
|
||||||
setScheduleLastRunLog(s.last_run_log || null)
|
setScheduleLastRunLog(s.last_run_log || null)
|
||||||
setScheduleLastRunRebootRequired(s.last_run_reboot_required === true)
|
setScheduleLastRunRebootRequired(s.last_run_reboot_required === true)
|
||||||
setScheduleLastRunRebootPackages(Array.isArray(s.last_run_reboot_packages)
|
setScheduleLastRunRebootPackages(Array.isArray(s.last_run_reboot_packages)
|
||||||
@@ -2430,6 +2449,9 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
const renderScheduleRunDetails = (indented = false) => {
|
const renderScheduleRunDetails = (indented = false) => {
|
||||||
if (!scheduleLastRunAt || !selectedVM) return null
|
if (!scheduleLastRunAt || !selectedVM) return null
|
||||||
const spacing = indented ? "pl-4" : ""
|
const spacing = indented ? "pl-4" : ""
|
||||||
|
const scheduleLastRunReasonText = scheduleLastRunReasonCode === "stopped_container"
|
||||||
|
? t("vmLxc.scheduled.reasonStoppedContainer")
|
||||||
|
: scheduleLastRunReason
|
||||||
return (
|
return (
|
||||||
<div className={`space-y-1.5 ${spacing}`}>
|
<div className={`space-y-1.5 ${spacing}`}>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
@@ -2448,9 +2470,9 @@ const handleDownloadLogs = async (vmid: number, vmName: string) => {
|
|||||||
</span></>
|
</span></>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{scheduleLastRunReason && (
|
{scheduleLastRunReasonText && (
|
||||||
<div className="text-xs text-muted-foreground break-words">
|
<div className="text-xs text-muted-foreground break-words">
|
||||||
{scheduleLastRunReason}
|
{scheduleLastRunReasonText}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{scheduleLastRunRebootRequired && (
|
{scheduleLastRunRebootRequired && (
|
||||||
|
|||||||
@@ -1288,6 +1288,7 @@
|
|||||||
"runFailed": "✗ failed",
|
"runFailed": "✗ failed",
|
||||||
"runDeferred": "held for safety",
|
"runDeferred": "held for safety",
|
||||||
"runSkipped": "nothing pending",
|
"runSkipped": "nothing pending",
|
||||||
|
"reasonStoppedContainer": "Container is stopped; scheduled update was skipped.",
|
||||||
"releaseDelaySummary": "{days}-day release hold",
|
"releaseDelaySummary": "{days}-day release hold",
|
||||||
"releaseDelayLabel": "Wait after a new app release",
|
"releaseDelayLabel": "Wait after a new app release",
|
||||||
"releaseDelayNone": "No waiting period",
|
"releaseDelayNone": "No waiting period",
|
||||||
|
|||||||
@@ -1288,6 +1288,7 @@
|
|||||||
"runFailed": "✗ zlyhalo",
|
"runFailed": "✗ zlyhalo",
|
||||||
"runDeferred": "odložené z bezpečnostných dôvodov",
|
"runDeferred": "odložené z bezpečnostných dôvodov",
|
||||||
"runSkipped": "nič nečaká",
|
"runSkipped": "nič nečaká",
|
||||||
|
"reasonStoppedContainer": "Kontajner je vypnutý; plánovaná aktualizácia sa preskočila.",
|
||||||
"releaseDelaySummary": "odklad {days} deň/dní",
|
"releaseDelaySummary": "odklad {days} deň/dní",
|
||||||
"releaseDelayLabel": "Počkať po novej verzii aplikácie",
|
"releaseDelayLabel": "Počkať po novej verzii aplikácie",
|
||||||
"releaseDelayNone": "Bez čakacej doby",
|
"releaseDelayNone": "Bez čakacej doby",
|
||||||
|
|||||||
@@ -22700,6 +22700,7 @@ def _run_scheduled_update(vmid: int, sched: dict) -> dict:
|
|||||||
requested_target = sched.get("target") or "both"
|
requested_target = sched.get("target") or "both"
|
||||||
deferred_targets: list[str] = []
|
deferred_targets: list[str] = []
|
||||||
reasons: list[str] = []
|
reasons: list[str] = []
|
||||||
|
reason_code: str | None = None
|
||||||
reboot_required: bool | None = None
|
reboot_required: bool | None = None
|
||||||
reboot_packages: list[str] = []
|
reboot_packages: list[str] = []
|
||||||
reboot_check_error: str | None = None
|
reboot_check_error: str | None = None
|
||||||
@@ -22751,6 +22752,7 @@ def _run_scheduled_update(vmid: int, sched: dict) -> dict:
|
|||||||
'status': status,
|
'status': status,
|
||||||
'target': actual_target,
|
'target': actual_target,
|
||||||
'reason': reason,
|
'reason': reason,
|
||||||
|
'reason_code': reason_code,
|
||||||
'run_id': run_id,
|
'run_id': run_id,
|
||||||
'requested_targets': requested_targets,
|
'requested_targets': requested_targets,
|
||||||
'executed_targets': list(executed),
|
'executed_targets': list(executed),
|
||||||
@@ -22763,6 +22765,7 @@ def _run_scheduled_update(vmid: int, sched: dict) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if not originally_running:
|
if not originally_running:
|
||||||
|
reason_code = 'stopped_container'
|
||||||
reasons.append('container is stopped; scheduled update was skipped')
|
reasons.append('container is stopped; scheduled update was skipped')
|
||||||
return finish('skipped', requested_target, [])
|
return finish('skipped', requested_target, [])
|
||||||
|
|
||||||
@@ -22978,6 +22981,7 @@ def _run_scheduled_update(vmid: int, sched: dict) -> dict:
|
|||||||
timeout=60 * 60,
|
timeout=60 * 60,
|
||||||
)
|
)
|
||||||
if _scheduled_runner_reported_stopped(r, log_path, scheduled_stopped_marker):
|
if _scheduled_runner_reported_stopped(r, log_path, scheduled_stopped_marker):
|
||||||
|
reason_code = 'stopped_container'
|
||||||
reasons.append('container was stopped before scheduled update could begin')
|
reasons.append('container was stopped before scheduled update could begin')
|
||||||
return finish('skipped', target, [])
|
return finish('skipped', target, [])
|
||||||
reboot_required, reboot_packages, reboot_check_error = (
|
reboot_required, reboot_packages, reboot_check_error = (
|
||||||
@@ -23055,12 +23059,14 @@ def _scheduler_loop():
|
|||||||
status = outcome.get('status') or 'failure'
|
status = outcome.get('status') or 'failure'
|
||||||
actual_target = outcome.get('target') or 'both'
|
actual_target = outcome.get('target') or 'both'
|
||||||
reason = outcome.get('reason')
|
reason = outcome.get('reason')
|
||||||
|
reason_code = outcome.get('reason_code')
|
||||||
try:
|
try:
|
||||||
lxc_apps.record_schedule_run(
|
lxc_apps.record_schedule_run(
|
||||||
_vmid,
|
_vmid,
|
||||||
status,
|
status,
|
||||||
actual_target,
|
actual_target,
|
||||||
reason,
|
reason,
|
||||||
|
reason_code=reason_code,
|
||||||
log_name=outcome.get('log_name'),
|
log_name=outcome.get('log_name'),
|
||||||
reboot_required=outcome.get('reboot_required'),
|
reboot_required=outcome.get('reboot_required'),
|
||||||
reboot_packages=outcome.get('reboot_packages'),
|
reboot_packages=outcome.get('reboot_packages'),
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ _VALID_UPSTREAM_TYPES = ("github", "http_json", "docker_hub")
|
|||||||
# `*/N` step, and comma lists — that covers every preset the UI
|
# `*/N` step, and comma lists — that covers every preset the UI
|
||||||
# exposes and the freeform "custom" text field.
|
# exposes and the freeform "custom" text field.
|
||||||
_VALID_SCHEDULE_TARGETS = ("os", "app", "both")
|
_VALID_SCHEDULE_TARGETS = ("os", "app", "both")
|
||||||
|
_SCHEDULE_REASON_CODES = frozenset({"stopped_container"})
|
||||||
_SCHEDULE_TARGET_ID_RE = re.compile(
|
_SCHEDULE_TARGET_ID_RE = re.compile(
|
||||||
r"^(?:os|apps|oci_image|app:[A-Za-z0-9_-]{1,64}|docker-engine|docker-(?:compose|container):[A-Za-z0-9][A-Za-z0-9_.-]{0,127}|docker-unit:[a-f0-9]{20})$"
|
r"^(?:os|apps|oci_image|app:[A-Za-z0-9_-]{1,64}|docker-engine|docker-(?:compose|container):[A-Za-z0-9][A-Za-z0-9_.-]{0,127}|docker-unit:[a-f0-9]{20})$"
|
||||||
)
|
)
|
||||||
@@ -3918,6 +3919,7 @@ def record_schedule_run(
|
|||||||
target: str,
|
target: str,
|
||||||
reason: Optional[str] = None,
|
reason: Optional[str] = None,
|
||||||
*,
|
*,
|
||||||
|
reason_code: Optional[str] = None,
|
||||||
log_name: Optional[str] = None,
|
log_name: Optional[str] = None,
|
||||||
reboot_required: Optional[bool] = None,
|
reboot_required: Optional[bool] = None,
|
||||||
reboot_packages: Optional[list[str]] = None,
|
reboot_packages: Optional[list[str]] = None,
|
||||||
@@ -3937,6 +3939,10 @@ def record_schedule_run(
|
|||||||
sidecar["schedule"]["last_run_reason"] = str(reason)[:300]
|
sidecar["schedule"]["last_run_reason"] = str(reason)[:300]
|
||||||
else:
|
else:
|
||||||
sidecar["schedule"].pop("last_run_reason", None)
|
sidecar["schedule"].pop("last_run_reason", None)
|
||||||
|
if reason_code in _SCHEDULE_REASON_CODES:
|
||||||
|
sidecar["schedule"]["last_run_reason_code"] = reason_code
|
||||||
|
else:
|
||||||
|
sidecar["schedule"].pop("last_run_reason_code", None)
|
||||||
if log_name:
|
if log_name:
|
||||||
sidecar["schedule"]["last_run_log"] = os.path.basename(str(log_name))[:220]
|
sidecar["schedule"]["last_run_log"] = os.path.basename(str(log_name))[:220]
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ class ScheduledUpdateRecordTests(unittest.TestCase):
|
|||||||
self.vmid,
|
self.vmid,
|
||||||
'success',
|
'success',
|
||||||
'both',
|
'both',
|
||||||
|
reason='container is stopped; scheduled update was skipped',
|
||||||
|
reason_code='stopped_container',
|
||||||
log_name='../9911-scheduled-' + ('a' * 32) + '.log',
|
log_name='../9911-scheduled-' + ('a' * 32) + '.log',
|
||||||
reboot_required=True,
|
reboot_required=True,
|
||||||
reboot_packages=['linux-image-amd64', 'libc6'],
|
reboot_packages=['linux-image-amd64', 'libc6'],
|
||||||
@@ -43,11 +45,31 @@ class ScheduledUpdateRecordTests(unittest.TestCase):
|
|||||||
'9911-scheduled-' + ('a' * 32) + '.log',
|
'9911-scheduled-' + ('a' * 32) + '.log',
|
||||||
)
|
)
|
||||||
self.assertTrue(schedule['last_run_reboot_required'])
|
self.assertTrue(schedule['last_run_reboot_required'])
|
||||||
|
self.assertEqual(schedule['last_run_reason_code'], 'stopped_container')
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
schedule['last_run_reboot_packages'],
|
schedule['last_run_reboot_packages'],
|
||||||
['linux-image-amd64', 'libc6'],
|
['linux-image-amd64', 'libc6'],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_record_rejects_unknown_reason_code_and_clears_the_previous_one(self):
|
||||||
|
self.assertTrue(lxc_apps.record_schedule_run(
|
||||||
|
self.vmid,
|
||||||
|
'skipped',
|
||||||
|
'os',
|
||||||
|
reason='container is stopped; scheduled update was skipped',
|
||||||
|
reason_code='stopped_container',
|
||||||
|
))
|
||||||
|
self.assertTrue(lxc_apps.record_schedule_run(
|
||||||
|
self.vmid,
|
||||||
|
'failure',
|
||||||
|
'os',
|
||||||
|
reason='update runner exited with code 9',
|
||||||
|
reason_code='untrusted_value',
|
||||||
|
))
|
||||||
|
schedule = lxc_apps._read_sidecar(self.vmid)['schedule']
|
||||||
|
self.assertEqual(schedule['last_run_reason'], 'update runner exited with code 9')
|
||||||
|
self.assertNotIn('last_run_reason_code', schedule)
|
||||||
|
|
||||||
def test_lifecycle_clear_preserves_run_and_log(self):
|
def test_lifecycle_clear_preserves_run_and_log(self):
|
||||||
lxc_apps.record_schedule_run(
|
lxc_apps.record_schedule_run(
|
||||||
self.vmid,
|
self.vmid,
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Execute the real scheduled-run detail JSX: a known backend reason code is
|
||||||
|
// localized, while an unknown/missing code keeps its raw diagnostic text.
|
||||||
|
const assert = require('node:assert/strict')
|
||||||
|
const fs = require('node:fs')
|
||||||
|
const path = require('node:path')
|
||||||
|
const vm = require('node:vm')
|
||||||
|
const root = path.resolve(__dirname, '../../AppImage')
|
||||||
|
const ts = require(path.join(root, 'node_modules/typescript'))
|
||||||
|
const source = fs.readFileSync(path.join(root, 'components/virtual-machines.tsx'), 'utf8')
|
||||||
|
const tree = ts.createSourceFile('vm.tsx', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
||||||
|
let declaration
|
||||||
|
let legacyReasons
|
||||||
|
let reasonCodeHelper
|
||||||
|
function walk(node) {
|
||||||
|
if (ts.isVariableDeclaration(node) && node.name.getText(tree) === 'renderScheduleRunDetails') declaration = node.getText(tree)
|
||||||
|
if (ts.isVariableDeclaration(node) && node.name.getText(tree) === 'LEGACY_STOPPED_SCHEDULE_REASONS') legacyReasons = node.getText(tree)
|
||||||
|
if (ts.isVariableDeclaration(node) && node.name.getText(tree) === 'scheduleReasonCodeFromPersisted') reasonCodeHelper = node.getText(tree)
|
||||||
|
ts.forEachChild(node, walk)
|
||||||
|
}
|
||||||
|
walk(tree)
|
||||||
|
assert(declaration, 'scheduled run detail renderer must exist')
|
||||||
|
assert(legacyReasons, 'legacy stopped-reason list must exist')
|
||||||
|
assert(reasonCodeHelper, 'persisted schedule reason-code helper must exist')
|
||||||
|
|
||||||
|
function compile(context) {
|
||||||
|
const result = ts.transpileModule(`const ${declaration}; exports.render = renderScheduleRunDetails;`, {
|
||||||
|
compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX },
|
||||||
|
reportDiagnostics: true,
|
||||||
|
})
|
||||||
|
assert.equal(result.diagnostics.length, 0)
|
||||||
|
vm.runInNewContext(result.outputText, context)
|
||||||
|
return context.exports.render
|
||||||
|
}
|
||||||
|
|
||||||
|
function compileReasonCodeHelper() {
|
||||||
|
const result = ts.transpileModule(`const ${legacyReasons}; const ${reasonCodeHelper}; exports.reasonCode = scheduleReasonCodeFromPersisted;`, {
|
||||||
|
compilerOptions: { target: ts.ScriptTarget.ES2020, module: ts.ModuleKind.CommonJS },
|
||||||
|
reportDiagnostics: true,
|
||||||
|
})
|
||||||
|
assert.equal(result.diagnostics.length, 0)
|
||||||
|
const context = { exports: {}, Set }
|
||||||
|
vm.runInNewContext(result.outputText, context)
|
||||||
|
return context.exports.reasonCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function flattenText(node) {
|
||||||
|
if (Array.isArray(node)) return node.map(flattenText).join(' ')
|
||||||
|
if (!node || typeof node !== 'object') return typeof node === 'string' ? node : ''
|
||||||
|
return flattenText(node.props?.children)
|
||||||
|
}
|
||||||
|
|
||||||
|
const persistedReasonCode = compileReasonCodeHelper()
|
||||||
|
assert.equal(persistedReasonCode('stopped_container', 'anything'), 'stopped_container')
|
||||||
|
assert.equal(persistedReasonCode(null, 'Container is stopped; scheduled update was skipped'), 'stopped_container')
|
||||||
|
assert.equal(persistedReasonCode(null, 'container was stopped before scheduled update could begin'), 'stopped_container')
|
||||||
|
assert.equal(persistedReasonCode('future_reason', 'raw diagnostic'), 'future_reason')
|
||||||
|
assert.equal(persistedReasonCode(null, 'raw diagnostic'), null)
|
||||||
|
|
||||||
|
for (const locale of ['en', 'sk']) {
|
||||||
|
const messages = JSON.parse(fs.readFileSync(path.join(root, `messages/${locale}/common.json`), 'utf8'))
|
||||||
|
const t = (key, variables = {}) => {
|
||||||
|
const message = key.split('.').reduce((value, part) => value?.[part], messages)
|
||||||
|
return String(message ?? key).replace(/\{([^}]+)\}/g, (_, name) => String(variables[name] ?? `{${name}}`))
|
||||||
|
}
|
||||||
|
const context = {
|
||||||
|
exports: {}, t, selectedVM: { vmid: 100 }, scheduleLastRunAt: '2026-10-16T05:10:02Z',
|
||||||
|
scheduleLastRunStatus: 'skipped', scheduleLastRunReasonCode: 'stopped_container',
|
||||||
|
scheduleLastRunReason: 'container is stopped; scheduled update was skipped',
|
||||||
|
scheduleLastRunRebootRequired: false, scheduleLastRunRebootPackages: [], scheduleLastRunLog: null,
|
||||||
|
openScheduleLog() {}, AlertTriangle: 'AlertTriangle', FileText: 'FileText',
|
||||||
|
require: () => ({ jsx: (type, props) => ({ type, props }), jsxs: (type, props) => ({ type, props }), Fragment: 'Fragment' }),
|
||||||
|
}
|
||||||
|
const render = compile(context)
|
||||||
|
const translated = flattenText(render(true))
|
||||||
|
const expected = t('vmLxc.scheduled.reasonStoppedContainer')
|
||||||
|
assert(translated.includes(expected), `${locale}: known reason code must use the locale catalog`)
|
||||||
|
assert(!translated.includes(context.scheduleLastRunReason), `${locale}: raw stopped reason must not leak into UI`)
|
||||||
|
|
||||||
|
context.scheduleLastRunReasonCode = 'unknown_reason'
|
||||||
|
const fallback = flattenText(render(true))
|
||||||
|
assert(fallback.includes(context.scheduleLastRunReason), `${locale}: unknown code must retain the raw diagnostic`)
|
||||||
|
}
|
||||||
|
console.log('PASS scheduled reason i18n: EN/SK structured stopped reason and raw fallback')
|
||||||
@@ -355,6 +355,7 @@ class ScheduledStoppedContainerTests(unittest.TestCase):
|
|||||||
self.assertEqual(outcome["status"], "skipped")
|
self.assertEqual(outcome["status"], "skipped")
|
||||||
self.assertEqual(outcome["executed_targets"], [])
|
self.assertEqual(outcome["executed_targets"], [])
|
||||||
self.assertIn("container is stopped", outcome["reason"])
|
self.assertIn("container is stopped", outcome["reason"])
|
||||||
|
self.assertEqual(outcome["reason_code"], "stopped_container")
|
||||||
runner.assert_not_called()
|
runner.assert_not_called()
|
||||||
self.assertEqual(finalizer.call_args.kwargs["status"], "skipped")
|
self.assertEqual(finalizer.call_args.kwargs["status"], "skipped")
|
||||||
self.assertEqual(finalizer.call_args.kwargs["source"], "scheduled")
|
self.assertEqual(finalizer.call_args.kwargs["source"], "scheduled")
|
||||||
@@ -409,6 +410,7 @@ class ScheduledStoppedContainerTests(unittest.TestCase):
|
|||||||
self.assertEqual(outcome["status"], "skipped")
|
self.assertEqual(outcome["status"], "skipped")
|
||||||
self.assertEqual(outcome["executed_targets"], [])
|
self.assertEqual(outcome["executed_targets"], [])
|
||||||
self.assertIn("before scheduled update could begin", outcome["reason"])
|
self.assertIn("before scheduled update could begin", outcome["reason"])
|
||||||
|
self.assertEqual(outcome["reason_code"], "stopped_container")
|
||||||
self.assertEqual(runner.call_args.args[0], ["bash", "/bin/true"])
|
self.assertEqual(runner.call_args.args[0], ["bash", "/bin/true"])
|
||||||
self.assertEqual(runner.call_args.kwargs["env"]["EXECUTION_MODE"], "scheduled")
|
self.assertEqual(runner.call_args.kwargs["env"]["EXECUTION_MODE"], "scheduled")
|
||||||
self.assertTrue(runner.call_args.kwargs["env"]["SCHEDULED_STOPPED_MARKER"].startswith(
|
self.assertTrue(runner.call_args.kwargs["env"]["SCHEDULED_STOPPED_MARKER"].startswith(
|
||||||
|
|||||||
Reference in New Issue
Block a user