A backup job exiting zero proves the job ran. Truncated dumps, a mysqldump that hit a lock and gave up, and an archive of an empty directory all exit zero.
# nightly, after the dump: restore to scratch and assert on content
mysql -e 'DROP DATABASE IF EXISTS restore_check; CREATE DATABASE restore_check;'
gunzip -c /backups/shop-latest.sql.gz | mysql restore_check
n=$(mysql -N -e 'SELECT COUNT(*) FROM restore_check.orders;')
[ "$n" -lt 1000 ] && { echo "RESTORE CHECK FAILED: $n rows"; exit 1; }
Asserting on content rather than on the import succeeding is what catches the truncated dump, which imports cleanly and contains a third of the data. Record how long the restore takes as well — that number is the recovery time objective, and it is almost always longer than anyone has assumed.