pgbus 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +236 -0
- data/app/controllers/pgbus/events_controller.rb +98 -2
- data/app/views/pgbus/events/_pending_table.html.erb +148 -0
- data/app/views/pgbus/events/index.html.erb +21 -1
- data/config/locales/da.yml +109 -0
- data/config/locales/de.yml +109 -0
- data/config/locales/en.yml +47 -0
- data/config/locales/es.yml +109 -0
- data/config/locales/fi.yml +109 -0
- data/config/locales/fr.yml +109 -0
- data/config/locales/it.yml +109 -0
- data/config/locales/ja.yml +109 -0
- data/config/locales/nb.yml +109 -0
- data/config/locales/nl.yml +109 -0
- data/config/locales/pt.yml +109 -0
- data/config/locales/sv.yml +109 -0
- data/config/routes.rb +7 -0
- data/lib/pgbus/configuration.rb +2 -1
- data/lib/pgbus/event_bus/publisher.rb +28 -3
- data/lib/pgbus/testing/assertions.rb +59 -0
- data/lib/pgbus/testing/minitest.rb +32 -0
- data/lib/pgbus/testing/rspec.rb +76 -0
- data/lib/pgbus/testing.rb +123 -0
- data/lib/pgbus/version.rb +1 -1
- data/lib/pgbus/web/data_source.rb +147 -2
- data/lib/pgbus/web/stream_app.rb +7 -0
- data/lib/pgbus.rb +1 -0
- metadata +6 -1
data/config/locales/pt.yml
CHANGED
|
@@ -66,6 +66,12 @@ pt:
|
|
|
66
66
|
index:
|
|
67
67
|
discard_all: Descartar Tudo
|
|
68
68
|
discard_all_confirm: Descartar permanentemente todas as mensagens DLQ?
|
|
69
|
+
discard_selected: Descartar Selecionados
|
|
70
|
+
discard_selected_confirm: Descartar mensagens DLQ selecionadas?
|
|
71
|
+
discarded_selected:
|
|
72
|
+
one: 1 mensagem DLQ descartada.
|
|
73
|
+
other: "%{count} mensagens DLQ descartadas."
|
|
74
|
+
none_selected: Nenhuma mensagem selecionada.
|
|
69
75
|
retry_all: Tentar Novamente Tudo
|
|
70
76
|
retry_all_confirm: Tentar novamente todas as mensagens DLQ?
|
|
71
77
|
title: Fila de Mensagens Mortas
|
|
@@ -113,7 +119,34 @@ pt:
|
|
|
113
119
|
delete_title: Excluir
|
|
114
120
|
ok: OK
|
|
115
121
|
events:
|
|
122
|
+
flash:
|
|
123
|
+
discard_failed: Não foi possível descartar o evento.
|
|
124
|
+
discarded: Evento descartado.
|
|
125
|
+
discarded_selected:
|
|
126
|
+
one: 1 evento descartado.
|
|
127
|
+
other: "%{count} eventos descartados."
|
|
128
|
+
mark_handled_failed: Não foi possível marcar o evento como tratado.
|
|
129
|
+
marked_handled: Evento marcado como tratado.
|
|
130
|
+
none_selected: Nenhum evento selecionado.
|
|
131
|
+
payload_update_failed: Não foi possível atualizar o payload. Verifique se o JSON é válido.
|
|
132
|
+
payload_updated: Payload atualizado e evento reenfileirado.
|
|
133
|
+
replay_failed: Evento não encontrado ou não pôde ser reproduzido.
|
|
134
|
+
replayed: Evento reproduzido.
|
|
135
|
+
reroute_failed: Não foi possível redirecionar o evento.
|
|
136
|
+
rerouted: Evento redirecionado para o manipulador alvo.
|
|
116
137
|
index:
|
|
138
|
+
discard_all: Descartar Todos
|
|
139
|
+
discard_all_confirm: Descartar todos os eventos pendentes? Esta ação não pode ser desfeita.
|
|
140
|
+
discard_selected: Descartar Selecionados
|
|
141
|
+
discard_selected_confirm: Descartar eventos selecionados?
|
|
142
|
+
pending_empty: Nenhum evento pendente
|
|
143
|
+
pending_headers:
|
|
144
|
+
enqueued: Enfileirado
|
|
145
|
+
handler_queue: Fila do Manipulador
|
|
146
|
+
id: ID
|
|
147
|
+
reads: Leituras
|
|
148
|
+
routing_key: Chave de Roteamento
|
|
149
|
+
pending_title: Eventos Pendentes
|
|
117
150
|
processed_empty: Nenhum evento processado ainda
|
|
118
151
|
processed_headers:
|
|
119
152
|
event_id: ID do evento
|
|
@@ -127,6 +160,26 @@ pt:
|
|
|
127
160
|
queue: Fila
|
|
128
161
|
subscribers_title: Assinantes registrados
|
|
129
162
|
title: Eventos
|
|
163
|
+
pending_table:
|
|
164
|
+
arguments: Payload
|
|
165
|
+
discard: Descartar
|
|
166
|
+
discard_confirm: Descartar este evento? Ele será arquivado.
|
|
167
|
+
edit_payload: Editar e Tentar Novamente
|
|
168
|
+
edit_payload_confirm: Atualizar payload e reenfileirar este evento?
|
|
169
|
+
edit_payload_label: 'Payload JSON:'
|
|
170
|
+
event_id: 'ID do Evento:'
|
|
171
|
+
full_json_payload: Payload JSON Completo
|
|
172
|
+
headers_section: Cabeçalhos
|
|
173
|
+
mark_handled: Marcar como Tratado
|
|
174
|
+
mark_handled_confirm: Marcar este evento como tratado? Ele será arquivado e ignorado na reprodução.
|
|
175
|
+
metadata: Metadados
|
|
176
|
+
metadata_labels:
|
|
177
|
+
last_read: 'Última leitura:'
|
|
178
|
+
read_count: 'Contagem de leituras:'
|
|
179
|
+
visible_at: 'Visível em:'
|
|
180
|
+
reroute: Redirecionar
|
|
181
|
+
reroute_confirm: Redirecionar este evento para um manipulador diferente?
|
|
182
|
+
reroute_label: 'Manipulador alvo:'
|
|
130
183
|
show:
|
|
131
184
|
back: Voltar para eventos
|
|
132
185
|
labels:
|
|
@@ -136,6 +189,9 @@ pt:
|
|
|
136
189
|
not_found: Evento não encontrado
|
|
137
190
|
title: Evento %{event_id}
|
|
138
191
|
helpers:
|
|
192
|
+
bulk_select_all: Selecionar todos
|
|
193
|
+
bulk_select_row: Selecionar %{id}
|
|
194
|
+
bulk_selected: selecionado
|
|
139
195
|
paused_badge: Pausado
|
|
140
196
|
queue_badge:
|
|
141
197
|
dlq: DLQ
|
|
@@ -150,11 +206,22 @@ pt:
|
|
|
150
206
|
show:
|
|
151
207
|
charts:
|
|
152
208
|
failed_to_load: Falha ao carregar dados do gráfico
|
|
209
|
+
latency: Latência da Fila (ms)
|
|
210
|
+
latency_avg: Média
|
|
211
|
+
latency_p95: P95
|
|
153
212
|
no_data: Sem dados ainda
|
|
154
213
|
series_name: Tarefas/min
|
|
155
214
|
status_distribution: Distribuição de status
|
|
156
215
|
throughput: Taxa de transferência (tarefas/min)
|
|
157
216
|
description_html: Métricas de desempenho de trabalho para os últimos %{range}
|
|
217
|
+
latency_by_queue:
|
|
218
|
+
empty: Ainda não há dados de latência
|
|
219
|
+
headers:
|
|
220
|
+
avg: Média (ms)
|
|
221
|
+
count: Contagem
|
|
222
|
+
p95: P95 (ms)
|
|
223
|
+
queue: Fila
|
|
224
|
+
title: Latência por Fila
|
|
158
225
|
slowest:
|
|
159
226
|
empty: Nenhuma estatística de tarefa ainda
|
|
160
227
|
headers:
|
|
@@ -163,11 +230,32 @@ pt:
|
|
|
163
230
|
job_class: Classe da tarefa
|
|
164
231
|
max: Máximo
|
|
165
232
|
title: Classes de Trabalho Mais Lentas (duração média)
|
|
233
|
+
streams:
|
|
234
|
+
summary:
|
|
235
|
+
active: Ativo
|
|
236
|
+
avg_fanout: Média de Ramificações
|
|
237
|
+
broadcasts: Transmissões
|
|
238
|
+
connects: Conexões
|
|
239
|
+
disconnects: Desconexões
|
|
240
|
+
title: Streams em Tempo Real
|
|
241
|
+
top:
|
|
242
|
+
empty: Nenhuma atividade de stream registrada na janela selecionada
|
|
243
|
+
headers:
|
|
244
|
+
avg_fanout: Média de Ramificações
|
|
245
|
+
avg_ms: Média de Despacho
|
|
246
|
+
broadcasts: Transmissões
|
|
247
|
+
stream: Stream
|
|
248
|
+
title: Principais Streams por Volume de Transmissão
|
|
166
249
|
summary:
|
|
167
250
|
avg_duration: Duração Média
|
|
251
|
+
avg_latency: Latência Média
|
|
252
|
+
avg_retries: Média de Tentativas
|
|
168
253
|
dead_lettered: Cartas Mortas
|
|
169
254
|
failed: Falhou
|
|
170
255
|
max_duration: Duração Máxima
|
|
256
|
+
p50_latency: Latência P50
|
|
257
|
+
p95_latency: Latência P95
|
|
258
|
+
p99_latency: Latência P99
|
|
171
259
|
succeeded: Bem-sucedido
|
|
172
260
|
total_jobs: Total de Trabalhos
|
|
173
261
|
time_ranges:
|
|
@@ -222,6 +310,12 @@ pt:
|
|
|
222
310
|
discard_all: Descartar Todos
|
|
223
311
|
discard_all_confirm: Descartar todos os trabalhos falhados?
|
|
224
312
|
discard_all_enqueued_notice: Descartados %{count} trabalhos na fila e bloqueios liberados.
|
|
313
|
+
discard_selected: Descartar Selecionados
|
|
314
|
+
discard_selected_confirm: Descartar itens selecionados?
|
|
315
|
+
discarded_selected:
|
|
316
|
+
one: 1 item selecionado descartado.
|
|
317
|
+
other: "%{count} itens selecionados descartados."
|
|
318
|
+
none_selected: Nenhum item selecionado.
|
|
225
319
|
retry_all: Tentar Novamente Todos
|
|
226
320
|
retry_all_confirm: Tentar novamente todos os trabalhos falhados?
|
|
227
321
|
title: Trabalhos
|
|
@@ -259,13 +353,28 @@ pt:
|
|
|
259
353
|
toggle_menu: Alternar menu
|
|
260
354
|
locks:
|
|
261
355
|
index:
|
|
356
|
+
all_locks_discarded:
|
|
357
|
+
one: 1 bloqueio descartado.
|
|
358
|
+
other: "%{count} bloqueios descartados."
|
|
262
359
|
description: Bloqueios de exclusividade ativos impedindo a execução duplicada do trabalho
|
|
360
|
+
discard: Descartar
|
|
361
|
+
discard_all: Descartar Todos
|
|
362
|
+
discard_all_confirm: Descartar permanentemente todos os bloqueios? Isso pode permitir a execução duplicada de trabalhos.
|
|
363
|
+
discard_confirm: Descartar este bloqueio? O trabalho associado pode ser enfileirado novamente.
|
|
364
|
+
discard_selected: Descartar Selecionados
|
|
365
|
+
discard_selected_confirm: Descartar bloqueios selecionados?
|
|
263
366
|
empty: Nenhum bloqueio ativo
|
|
264
367
|
headers:
|
|
265
368
|
age: Idade
|
|
266
369
|
lock_key: Chave de Bloqueio
|
|
267
370
|
msg_id: ID da mensagem
|
|
268
371
|
queue_name: Fila
|
|
372
|
+
lock_discard_failed: Não foi possível descartar o bloqueio.
|
|
373
|
+
lock_discarded: Bloqueio descartado.
|
|
374
|
+
locks_discarded:
|
|
375
|
+
one: 1 bloqueio descartado.
|
|
376
|
+
other: "%{count} bloqueios descartados."
|
|
377
|
+
none_selected: Nenhum bloqueio selecionado.
|
|
269
378
|
title: Chaves de unicidade
|
|
270
379
|
outbox:
|
|
271
380
|
index:
|
data/config/locales/sv.yml
CHANGED
|
@@ -66,6 +66,12 @@ sv:
|
|
|
66
66
|
index:
|
|
67
67
|
discard_all: Kassera alla
|
|
68
68
|
discard_all_confirm: Kassera permanent alla DLQ-meddelanden?
|
|
69
|
+
discard_selected: Kassera valda
|
|
70
|
+
discard_selected_confirm: Kassera valda DLQ-meddelanden?
|
|
71
|
+
discarded_selected:
|
|
72
|
+
one: Kasserade 1 DLQ-meddelande.
|
|
73
|
+
other: Kasserade %{count} DLQ-meddelanden.
|
|
74
|
+
none_selected: Inga meddelanden valda.
|
|
69
75
|
retry_all: Försök igen alla
|
|
70
76
|
retry_all_confirm: Försök igen alla DLQ-meddelanden?
|
|
71
77
|
title: Dead Letter-kö
|
|
@@ -113,7 +119,34 @@ sv:
|
|
|
113
119
|
delete_title: Ta bort
|
|
114
120
|
ok: OK
|
|
115
121
|
events:
|
|
122
|
+
flash:
|
|
123
|
+
discard_failed: Kunde inte kassera händelsen.
|
|
124
|
+
discarded: Händelse kasserad.
|
|
125
|
+
discarded_selected:
|
|
126
|
+
one: Kasserade 1 händelse.
|
|
127
|
+
other: Kasserade %{count} händelser.
|
|
128
|
+
mark_handled_failed: Kunde inte markera händelsen som hanterad.
|
|
129
|
+
marked_handled: Händelse markerad som hanterad.
|
|
130
|
+
none_selected: Inga händelser valda.
|
|
131
|
+
payload_update_failed: Kunde inte uppdatera payload. Kontrollera att JSON är giltig.
|
|
132
|
+
payload_updated: Payload uppdaterad och händelsen återköad.
|
|
133
|
+
replay_failed: Händelsen hittades inte eller kunde inte spelas upp.
|
|
134
|
+
replayed: Händelsen spelades upp.
|
|
135
|
+
reroute_failed: Kunde inte omdirigera händelsen.
|
|
136
|
+
rerouted: Händelsen omdirigerades till målhanteraren.
|
|
116
137
|
index:
|
|
138
|
+
discard_all: Kassera alla
|
|
139
|
+
discard_all_confirm: Kassera alla väntande händelser? Detta kan inte ångras.
|
|
140
|
+
discard_selected: Kassera valda
|
|
141
|
+
discard_selected_confirm: Kassera valda händelser?
|
|
142
|
+
pending_empty: Inga väntande händelser
|
|
143
|
+
pending_headers:
|
|
144
|
+
enqueued: Köad
|
|
145
|
+
handler_queue: Handler-kö
|
|
146
|
+
id: ID
|
|
147
|
+
reads: Läsningar
|
|
148
|
+
routing_key: Routing-nyckel
|
|
149
|
+
pending_title: Väntande händelser
|
|
117
150
|
processed_empty: Inga händelser behandlade än
|
|
118
151
|
processed_headers:
|
|
119
152
|
event_id: Händelse-ID
|
|
@@ -127,6 +160,26 @@ sv:
|
|
|
127
160
|
queue: Kö
|
|
128
161
|
subscribers_title: Registrerade prenumeranter
|
|
129
162
|
title: Händelser
|
|
163
|
+
pending_table:
|
|
164
|
+
arguments: Payload
|
|
165
|
+
discard: Kassera
|
|
166
|
+
discard_confirm: Kassera denna händelse? Den kommer att arkiveras.
|
|
167
|
+
edit_payload: Redigera & Försök igen
|
|
168
|
+
edit_payload_confirm: Uppdatera payload och återköa denna händelse?
|
|
169
|
+
edit_payload_label: 'JSON Payload:'
|
|
170
|
+
event_id: 'Händelse-ID:'
|
|
171
|
+
full_json_payload: Fullständig JSON Payload
|
|
172
|
+
headers_section: Headers
|
|
173
|
+
mark_handled: Markera som hanterad
|
|
174
|
+
mark_handled_confirm: Markera denna händelse som hanterad? Den kommer att arkiveras och hoppas över vid uppspelning.
|
|
175
|
+
metadata: Metadata
|
|
176
|
+
metadata_labels:
|
|
177
|
+
last_read: 'Senast läst:'
|
|
178
|
+
read_count: 'Läsantal:'
|
|
179
|
+
visible_at: 'Synlig vid:'
|
|
180
|
+
reroute: Omdirigera
|
|
181
|
+
reroute_confirm: Omdirigera denna händelse till en annan hanterare?
|
|
182
|
+
reroute_label: 'Målhanterare:'
|
|
130
183
|
show:
|
|
131
184
|
back: Tillbaka till händelser
|
|
132
185
|
labels:
|
|
@@ -136,6 +189,9 @@ sv:
|
|
|
136
189
|
not_found: Händelse hittades inte
|
|
137
190
|
title: Händelse %{event_id}
|
|
138
191
|
helpers:
|
|
192
|
+
bulk_select_all: Markera alla
|
|
193
|
+
bulk_select_row: Markera %{id}
|
|
194
|
+
bulk_selected: valda
|
|
139
195
|
paused_badge: Pausad
|
|
140
196
|
queue_badge:
|
|
141
197
|
dlq: DLQ
|
|
@@ -150,11 +206,22 @@ sv:
|
|
|
150
206
|
show:
|
|
151
207
|
charts:
|
|
152
208
|
failed_to_load: Misslyckades att ladda diagramdata
|
|
209
|
+
latency: Köfördröjning (ms)
|
|
210
|
+
latency_avg: Genomsnitt
|
|
211
|
+
latency_p95: P95
|
|
153
212
|
no_data: Inga data än
|
|
154
213
|
series_name: Jobb/min
|
|
155
214
|
status_distribution: Statusfördelning
|
|
156
215
|
throughput: Genomströmning (jobb/min)
|
|
157
216
|
description_html: Jobbprestandamått för de senaste %{range}
|
|
217
|
+
latency_by_queue:
|
|
218
|
+
empty: Ingen fördröjningsdata än
|
|
219
|
+
headers:
|
|
220
|
+
avg: Genomsnitt (ms)
|
|
221
|
+
count: Antal
|
|
222
|
+
p95: P95 (ms)
|
|
223
|
+
queue: Kö
|
|
224
|
+
title: Fördröjning per kö
|
|
158
225
|
slowest:
|
|
159
226
|
empty: Inga jobbstatistik än
|
|
160
227
|
headers:
|
|
@@ -163,11 +230,32 @@ sv:
|
|
|
163
230
|
job_class: Jobbklass
|
|
164
231
|
max: Max
|
|
165
232
|
title: Långsammaste jobbklasser (genomsnittlig varaktighet)
|
|
233
|
+
streams:
|
|
234
|
+
summary:
|
|
235
|
+
active: Aktiv
|
|
236
|
+
avg_fanout: Genomsnittlig spridning
|
|
237
|
+
broadcasts: Sändningar
|
|
238
|
+
connects: Anslutningar
|
|
239
|
+
disconnects: Frånkopplingar
|
|
240
|
+
title: Strömmar i realtid
|
|
241
|
+
top:
|
|
242
|
+
empty: Ingen strömaktivitet registrerad i det valda fönstret
|
|
243
|
+
headers:
|
|
244
|
+
avg_fanout: Genomsnittlig spridning
|
|
245
|
+
avg_ms: Genomsnittlig utsändning
|
|
246
|
+
broadcasts: Sändningar
|
|
247
|
+
stream: Ström
|
|
248
|
+
title: Toppströmmar efter sändningsvolym
|
|
166
249
|
summary:
|
|
167
250
|
avg_duration: Genomsnittlig varaktighet
|
|
251
|
+
avg_latency: Genomsnittlig fördröjning
|
|
252
|
+
avg_retries: Genomsnittliga omförsök
|
|
168
253
|
dead_lettered: Dead Lettered
|
|
169
254
|
failed: Misslyckades
|
|
170
255
|
max_duration: Maximal varaktighet
|
|
256
|
+
p50_latency: P50-fördröjning
|
|
257
|
+
p95_latency: P95-fördröjning
|
|
258
|
+
p99_latency: P99-fördröjning
|
|
171
259
|
succeeded: Lyckades
|
|
172
260
|
total_jobs: Totalt antal jobb
|
|
173
261
|
time_ranges:
|
|
@@ -222,6 +310,12 @@ sv:
|
|
|
222
310
|
discard_all: Kassera alla
|
|
223
311
|
discard_all_confirm: Kassera alla misslyckade jobb?
|
|
224
312
|
discard_all_enqueued_notice: Kasserade %{count} köade jobb och frigjorde deras lås.
|
|
313
|
+
discard_selected: Kassera valda
|
|
314
|
+
discard_selected_confirm: Kassera valda objekt?
|
|
315
|
+
discarded_selected:
|
|
316
|
+
one: Kasserade 1 valt objekt.
|
|
317
|
+
other: Kasserade %{count} valda objekt.
|
|
318
|
+
none_selected: Inga objekt valda.
|
|
225
319
|
retry_all: Försök igen alla
|
|
226
320
|
retry_all_confirm: Försök igen alla misslyckade jobb?
|
|
227
321
|
title: Jobb
|
|
@@ -259,13 +353,28 @@ sv:
|
|
|
259
353
|
toggle_menu: Växla meny
|
|
260
354
|
locks:
|
|
261
355
|
index:
|
|
356
|
+
all_locks_discarded:
|
|
357
|
+
one: Kasserade 1 lås.
|
|
358
|
+
other: Kasserade %{count} lås.
|
|
262
359
|
description: Aktiva unika lås som förhindrar duplicerad jobbexekvering
|
|
360
|
+
discard: Kassera
|
|
361
|
+
discard_all: Kassera alla
|
|
362
|
+
discard_all_confirm: Kassera alla lås permanent? Detta kan tillåta duplicerad jobbexekvering.
|
|
363
|
+
discard_confirm: Kassera detta lås? Det associerade jobbet kan köas igen.
|
|
364
|
+
discard_selected: Kassera valda
|
|
365
|
+
discard_selected_confirm: Kassera valda lås?
|
|
263
366
|
empty: Inga aktiva lås
|
|
264
367
|
headers:
|
|
265
368
|
age: Ålder
|
|
266
369
|
lock_key: Låsningsnyckel
|
|
267
370
|
msg_id: Meddelande-ID
|
|
268
371
|
queue_name: Kö
|
|
372
|
+
lock_discard_failed: Kunde inte kassera lås.
|
|
373
|
+
lock_discarded: Lås kasserat.
|
|
374
|
+
locks_discarded:
|
|
375
|
+
one: Kasserade 1 lås.
|
|
376
|
+
other: Kasserade %{count} lås.
|
|
377
|
+
none_selected: Inga lås valda.
|
|
269
378
|
title: Unikhetsnycklar
|
|
270
379
|
outbox:
|
|
271
380
|
index:
|
data/config/routes.rb
CHANGED
data/lib/pgbus/configuration.rb
CHANGED
|
@@ -103,7 +103,7 @@ module Pgbus
|
|
|
103
103
|
:streams_default_retention, :streams_retention, :streams_heartbeat_interval,
|
|
104
104
|
:streams_max_connections, :streams_idle_timeout, :streams_listen_health_check_ms,
|
|
105
105
|
:streams_write_deadline_ms, :streams_falcon_streaming_body,
|
|
106
|
-
:streams_stats_enabled
|
|
106
|
+
:streams_stats_enabled, :streams_test_mode
|
|
107
107
|
|
|
108
108
|
def initialize
|
|
109
109
|
@database_url = nil
|
|
@@ -209,6 +209,7 @@ module Pgbus
|
|
|
209
209
|
# gates pgbus_job_stats recording) on purpose — operators
|
|
210
210
|
# usually want job stats on and stream stats off, or vice versa.
|
|
211
211
|
@streams_stats_enabled = false
|
|
212
|
+
@streams_test_mode = false
|
|
212
213
|
end
|
|
213
214
|
|
|
214
215
|
def queue_name(name)
|
|
@@ -1,12 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
3
5
|
module Pgbus
|
|
4
6
|
module EventBus
|
|
5
7
|
module Publisher
|
|
6
8
|
module_function
|
|
7
9
|
|
|
8
10
|
def publish(routing_key, payload, headers: nil, delay: 0)
|
|
9
|
-
event_data = build_event_data(payload)
|
|
11
|
+
event_data = build_event_data(payload, routing_key: routing_key)
|
|
12
|
+
|
|
13
|
+
if defined?(Pgbus::Testing) && !Pgbus::Testing.disabled?
|
|
14
|
+
event = Pgbus::Event.new(
|
|
15
|
+
event_id: event_data["event_id"],
|
|
16
|
+
payload: event_data["payload"],
|
|
17
|
+
published_at: event_data["published_at"] ? Time.parse(event_data["published_at"]) : nil,
|
|
18
|
+
routing_key: routing_key,
|
|
19
|
+
headers: headers
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
Pgbus::Testing.store.push_event(event)
|
|
23
|
+
|
|
24
|
+
if Pgbus::Testing.inline? && delay.to_i <= 0
|
|
25
|
+
Pgbus::EventBus::Registry.instance.handlers_for(routing_key).each do |subscriber|
|
|
26
|
+
subscriber.handler_class.new.handle(event)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return event_data
|
|
31
|
+
end
|
|
32
|
+
|
|
10
33
|
Pgbus.client.publish_to_topic(routing_key, event_data, headers: headers, delay: delay)
|
|
11
34
|
end
|
|
12
35
|
|
|
@@ -14,7 +37,7 @@ module Pgbus
|
|
|
14
37
|
publish(routing_key, payload, headers: headers, delay: delay)
|
|
15
38
|
end
|
|
16
39
|
|
|
17
|
-
def build_event_data(payload)
|
|
40
|
+
def build_event_data(payload, routing_key: nil)
|
|
18
41
|
event_id = SecureRandom.uuid
|
|
19
42
|
|
|
20
43
|
serialized_payload = if payload.respond_to?(:to_global_id)
|
|
@@ -25,11 +48,13 @@ module Pgbus
|
|
|
25
48
|
{ "value" => payload }
|
|
26
49
|
end
|
|
27
50
|
|
|
28
|
-
{
|
|
51
|
+
data = {
|
|
29
52
|
"event_id" => event_id,
|
|
30
53
|
"payload" => serialized_payload,
|
|
31
54
|
"published_at" => Time.now.utc.iso8601(6)
|
|
32
55
|
}
|
|
56
|
+
data["routing_key"] = routing_key if routing_key
|
|
57
|
+
data
|
|
33
58
|
end
|
|
34
59
|
end
|
|
35
60
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pgbus
|
|
4
|
+
module Testing
|
|
5
|
+
# Framework-agnostic assertion helpers. Included by both RSpec and Minitest
|
|
6
|
+
# integrations. Can also be included directly in any test class.
|
|
7
|
+
#
|
|
8
|
+
# include Pgbus::Testing::Assertions
|
|
9
|
+
#
|
|
10
|
+
# assert_pgbus_published(count: 1, routing_key: "orders.created") do
|
|
11
|
+
# Order.create!(...)
|
|
12
|
+
# end
|
|
13
|
+
module Assertions
|
|
14
|
+
def pgbus_published_events(routing_key: nil)
|
|
15
|
+
Pgbus::Testing.store.events(routing_key: routing_key)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def assert_pgbus_published(count:, routing_key: nil)
|
|
19
|
+
before = pgbus_published_events(routing_key: routing_key).size
|
|
20
|
+
yield
|
|
21
|
+
after = pgbus_published_events(routing_key: routing_key).size
|
|
22
|
+
actual = after - before
|
|
23
|
+
|
|
24
|
+
return if actual == count
|
|
25
|
+
|
|
26
|
+
suffix = routing_key ? " matching #{routing_key.inspect}" : ""
|
|
27
|
+
raise_assertion("Expected #{count} event(s) published#{suffix}, got #{actual}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def assert_no_pgbus_published(routing_key: nil)
|
|
31
|
+
before = pgbus_published_events(routing_key: routing_key).size
|
|
32
|
+
yield
|
|
33
|
+
after = pgbus_published_events(routing_key: routing_key).size
|
|
34
|
+
actual = after - before
|
|
35
|
+
|
|
36
|
+
return if actual.zero?
|
|
37
|
+
|
|
38
|
+
suffix = routing_key ? " matching #{routing_key.inspect}" : ""
|
|
39
|
+
raise_assertion("Expected no events published#{suffix}, got #{actual}")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Execute the block, capturing events, then dispatch all captured events
|
|
43
|
+
# to their registered handlers.
|
|
44
|
+
def perform_published_events
|
|
45
|
+
yield
|
|
46
|
+
Pgbus::Testing.store.drain!
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def raise_assertion(message)
|
|
52
|
+
# Use Minitest::Assertion if available, otherwise a generic RuntimeError
|
|
53
|
+
raise Minitest::Assertion, message if defined?(Minitest::Assertion)
|
|
54
|
+
|
|
55
|
+
raise message
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../testing"
|
|
4
|
+
|
|
5
|
+
module Pgbus
|
|
6
|
+
module Testing
|
|
7
|
+
# Minitest integration for Pgbus test helpers.
|
|
8
|
+
#
|
|
9
|
+
# Include in your test_helper.rb:
|
|
10
|
+
#
|
|
11
|
+
# require "pgbus/testing/minitest"
|
|
12
|
+
#
|
|
13
|
+
# class ActiveSupport::TestCase
|
|
14
|
+
# include Pgbus::Testing::MinitestHelpers
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# This provides:
|
|
18
|
+
# - Automatic fake mode + store clearing per test
|
|
19
|
+
# - assert_pgbus_published / assert_no_pgbus_published
|
|
20
|
+
# - perform_published_events
|
|
21
|
+
# - pgbus_published_events
|
|
22
|
+
module MinitestHelpers
|
|
23
|
+
include Pgbus::Testing::Assertions
|
|
24
|
+
|
|
25
|
+
def before_setup
|
|
26
|
+
Pgbus::Testing.fake!
|
|
27
|
+
Pgbus::Testing.store.clear!
|
|
28
|
+
super
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../testing"
|
|
4
|
+
|
|
5
|
+
RSpec::Matchers.define :have_published_event do |expected_routing_key|
|
|
6
|
+
supports_block_expectations
|
|
7
|
+
|
|
8
|
+
chain(:with_payload) { |payload| @expected_payload = payload }
|
|
9
|
+
chain(:with_headers) { |headers| @expected_headers = headers }
|
|
10
|
+
chain(:exactly) { |count| @expected_count = count }
|
|
11
|
+
|
|
12
|
+
match do |block|
|
|
13
|
+
@before = Pgbus::Testing.store.events(routing_key: expected_routing_key).dup
|
|
14
|
+
block.call
|
|
15
|
+
@after = Pgbus::Testing.store.events(routing_key: expected_routing_key)
|
|
16
|
+
@new_events = @after - @before
|
|
17
|
+
|
|
18
|
+
return false if @new_events.empty?
|
|
19
|
+
|
|
20
|
+
if @expected_payload
|
|
21
|
+
@new_events = @new_events.select { |e| values_match?(@expected_payload, e.payload) }
|
|
22
|
+
return false if @new_events.empty?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if @expected_headers
|
|
26
|
+
@new_events = @new_events.select { |e| values_match?(@expected_headers, e.headers) }
|
|
27
|
+
return false if @new_events.empty?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return false if @expected_count && @new_events.size != @expected_count
|
|
31
|
+
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
match_when_negated do |block|
|
|
36
|
+
@before = Pgbus::Testing.store.events(routing_key: expected_routing_key).dup
|
|
37
|
+
block.call
|
|
38
|
+
@after = Pgbus::Testing.store.events(routing_key: expected_routing_key)
|
|
39
|
+
@new_events = @after - @before
|
|
40
|
+
|
|
41
|
+
@new_events = @new_events.select { |e| values_match?(@expected_payload, e.payload) } if @expected_payload
|
|
42
|
+
|
|
43
|
+
@new_events = @new_events.select { |e| values_match?(@expected_headers, e.headers) } if @expected_headers
|
|
44
|
+
|
|
45
|
+
if @expected_count
|
|
46
|
+
@new_events.size != @expected_count
|
|
47
|
+
else
|
|
48
|
+
@new_events.empty?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
failure_message do
|
|
53
|
+
parts = ["expected block to publish a #{expected_routing_key.inspect} event"]
|
|
54
|
+
parts << "with payload #{@expected_payload.inspect}" if @expected_payload
|
|
55
|
+
parts << "with headers #{@expected_headers.inspect}" if @expected_headers
|
|
56
|
+
parts << "exactly #{@expected_count} time(s)" if @expected_count
|
|
57
|
+
|
|
58
|
+
published = @new_events&.size || 0
|
|
59
|
+
parts << "but #{published} matching event(s) were published"
|
|
60
|
+
|
|
61
|
+
if published.positive? && @expected_payload
|
|
62
|
+
actual_payloads = @after.map(&:payload)
|
|
63
|
+
parts << "actual payloads: #{actual_payloads.inspect}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
parts.join(", ")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
failure_message_when_negated do
|
|
70
|
+
"expected block not to publish a #{expected_routing_key.inspect} event, but #{@new_events.size} were published"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
RSpec.configure do |config|
|
|
75
|
+
config.include Pgbus::Testing::Assertions
|
|
76
|
+
end
|