Evita 500 senza SMTP e chiude gli incidenti overflow stale.

Reset password e mail replay usano deliver_mail; il health check overflow risolve tutte le fingerprint del kind, non solo quella sana.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 08:55:36 +02:00
co-authored by Cursor
parent b08049cf69
commit 645807b853
9 changed files with 104 additions and 3 deletions
@@ -54,6 +54,8 @@ module Ops
def process_finding(finding)
if finding.healthy
Ops::IncidentRecorder.resolve(fingerprint: finding.fingerprint)
# overflow usa fingerprint diverse (at_max / orphan_idle / budget) rispetto al check sano
Ops::IncidentRecorder.resolve_kind(finding.kind) if finding.kind == "stream_overflow"
else
Ops::IncidentRecorder.record(
kind: finding.kind,
@@ -10,6 +10,10 @@ module Ops
def resolve(fingerprint:)
new.resolve(fingerprint: fingerprint)
end
def resolve_kind(kind)
new.resolve_kind(kind)
end
end
def record(finding)
@@ -49,6 +53,10 @@ module Ops
Ops::Incident.open.where(fingerprint: fingerprint).find_each(&:resolve!)
end
def resolve_kind(kind)
Ops::Incident.open.where(kind: kind).find_each(&:resolve!)
end
private
def fingerprint_for(finding)
@@ -23,7 +23,7 @@ module Recordings
private
def deliver_expiring_soon(user)
Recordings::ReplayMailer.replay_expiring_soon(recording: @recording, recipient: user).deliver_now
MatchLiveTv.deliver_mail(Recordings::ReplayMailer.replay_expiring_soon(recording: @recording, recipient: user))
rescue EOFError => e
Rails.logger.warn("[Recordings::NotifyExpiring] SMTP EOF on close for #{user.email}: #{e.message}")
end
@@ -19,7 +19,7 @@ module Recordings
private
def deliver_replay_ready(user)
Recordings::ReplayMailer.replay_ready(recording: @recording, recipient: user).deliver_now
MatchLiveTv.deliver_mail(Recordings::ReplayMailer.replay_ready(recording: @recording, recipient: user))
rescue EOFError => e
# Aruba SMTP (465/SSL) chiude la socket prima del QUIT: la mail è già partita.
Rails.logger.warn("[Recordings::NotifyReady] SMTP EOF on close for #{user.email}: #{e.message}")
@@ -13,7 +13,7 @@ module Users
return if user.nil?
token = user.generate_password_reset!
UserMailer.password_reset(user, token).deliver_now
MatchLiveTv.deliver_mail(UserMailer.password_reset(user, token))
end
end
end
+11
View File
@@ -109,6 +109,17 @@ RSpec.describe "Account API", type: :request do
expect(user.reload.password_reset_digest).to be_present
end
it "non va in 500 se in produzione manca SMTP" do
allow(MatchLiveTv).to receive(:smtp_configured?).and_return(false)
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
expect {
post "/api/v1/auth/password/forgot", params: { email: user.email }
}.not_to change { ActionMailer::Base.deliveries.size }
expect(response).to have_http_status(:ok)
expect(user.reload.password_reset_digest).to be_present
end
it "returns the same message for unknown emails" do
expect {
post "/api/v1/auth/password/forgot", params: { email: "nobody@example.com" }
@@ -40,6 +40,44 @@ RSpec.describe Ops::HealthChecks do
expect(Ops::Incident.find_by(fingerprint: "disk_space:root").status).to eq("resolved")
end
it "chiude gli incidenti overflow anche se la fingerprint sana è diversa" do
Ops::Incident.create!(
kind: "stream_overflow",
severity: "warning",
status: "open",
title: "Capacità stream al massimo",
message: "overflow=3/3",
metadata: {},
fingerprint: "stream_overflow:at_max",
occurrence_count: 1,
first_seen_at: Time.current,
last_seen_at: Time.current
)
allow_any_instance_of(described_class).to receive(:check_stream_overflow).and_return(
described_class::Finding.new(
kind: "stream_overflow", severity: "info", healthy: true,
title: "Overflow streaming OK", message: "OK", metadata: {}, fingerprint: "stream_overflow:ok"
)
)
%i[
check_disk_root check_recordings_size check_postgres check_redis check_mediamtx check_garage
check_sidekiq_heartbeat check_sidekiq_dead check_http_rails check_rails_latency
].each do |method|
allow_any_instance_of(described_class).to receive(method).and_return(
described_class::Finding.new(
kind: "test", severity: "info", healthy: true,
title: "OK", message: "OK", metadata: {}, fingerprint: "#{method}:ok"
)
)
end
allow_any_instance_of(described_class).to receive(:public_check_due?).and_return(false)
described_class.new.call
expect(Ops::Incident.find_by(fingerprint: "stream_overflow:at_max").status).to eq("resolved")
end
end
describe "#summary" do
@@ -52,4 +52,28 @@ RSpec.describe Ops::IncidentRecorder do
expect(incident.reload.status).to eq("resolved")
end
end
describe ".resolve_kind" do
it "chiude tutti gli incidenti aperti di quel kind" do
at_max = described_class.record(
kind: "stream_overflow",
severity: "warning",
title: "Max",
message: "max",
fingerprint: "stream_overflow:at_max"
)
orphan = described_class.record(
kind: "stream_overflow",
severity: "warning",
title: "Idle",
message: "idle",
fingerprint: "stream_overflow:orphan_idle"
)
described_class.resolve_kind("stream_overflow")
expect(at_max.reload.status).to eq("resolved")
expect(orphan.reload.status).to eq("resolved")
end
end
end
@@ -27,4 +27,22 @@ RSpec.describe Recordings::NotifyReady do
expect(recording.reload.ready_notified_at).to be_present
end
it "marca notificato anche se in produzione manca SMTP" do
recording = Recording.create!(
stream_session: session,
team: team,
status: "ready",
expires_at: 10.days.from_now,
storage_key: "key"
)
allow(MatchLiveTv).to receive(:smtp_configured?).and_return(false)
allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
expect {
described_class.new(recording).call
}.not_to change { ActionMailer::Base.deliveries.size }
expect(recording.reload.ready_notified_at).to be_present
end
end