//go:build integration package mq import ( "context" "errors" "fmt" "os" "testing" "time" "git.ipao.vip/rogee/go-sip/internal/tenant" ) func TestLocalRabbitMQConfirmAckAndDeadLetter(t *testing.T) { url := os.Getenv("RABBITMQ_URL") if url == "" { t.Skip("RABBITMQ_URL is not configured") } broker, err := OpenWithPrefetch(url, "", 1) if err != nil { t.Fatal(err) } defer broker.Close() tenantKey := fmt.Sprintf("integration-%d", time.Now().UnixNano()) queue, err := broker.DeclareTenantQueue(tenantKey) if err != nil { t.Fatal(err) } routingKey, err := tenant.CommandRoutingKey(tenantKey) if err != nil { t.Fatal(err) } deadLetterQueue, err := tenant.DeadLetterQueue(tenantKey) if err != nil { t.Fatal(err) } body := []byte(`{"command_id":"integration-command"}`) ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() acked := make(chan struct{}, 1) consumeDone := make(chan error, 1) go func() { consumeDone <- broker.Consume(ctx, queue, func(_ context.Context, gotRoutingKey string, gotBody []byte) error { if gotRoutingKey != routingKey || string(gotBody) != string(body) { return fmt.Errorf("delivery mismatch: key=%q body=%q", gotRoutingKey, gotBody) } acked <- struct{}{} return nil }) }() if err := broker.Publish(ctx, DefaultExchange, routingKey, body); err != nil { t.Fatal(err) } select { case <-acked: case <-ctx.Done(): t.Fatal(ctx.Err()) } cancel() select { case <-consumeDone: case <-time.After(3 * time.Second): t.Fatal("consumer did not stop") } permanentCtx, permanentCancel := context.WithTimeout(context.Background(), 15*time.Second) defer permanentCancel() permanentSeen := make(chan struct{}, 1) permanentDone := make(chan error, 1) go func() { permanentDone <- broker.Consume(permanentCtx, queue, func(_ context.Context, _ string, _ []byte) error { permanentSeen <- struct{}{} return Permanent(errors.New("synthetic permanent command error")) }) }() if err := broker.Publish(permanentCtx, DefaultExchange, routingKey, body); err != nil { t.Fatal(err) } select { case <-permanentSeen: case <-permanentCtx.Done(): t.Fatal(permanentCtx.Err()) } permanentCancel() select { case <-permanentDone: case <-time.After(3 * time.Second): t.Fatal("permanent consumer did not stop") } dlqCtx, dlqCancel := context.WithTimeout(context.Background(), 15*time.Second) defer dlqCancel() deadLettered := make(chan struct{}, 1) dlqDone := make(chan error, 1) go func() { dlqDone <- broker.Consume(dlqCtx, deadLetterQueue, func(_ context.Context, _ string, gotBody []byte) error { if string(gotBody) != string(body) { return fmt.Errorf("dead-letter body mismatch: %q", gotBody) } deadLettered <- struct{}{} return nil }) }() select { case <-deadLettered: dlqCancel() case <-dlqCtx.Done(): t.Fatal(dlqCtx.Err()) } select { case <-dlqDone: case <-time.After(3 * time.Second): t.Fatal("dead-letter consumer did not stop") } }