Skip to content

feat: add test hook button for notification hooks - #1323

Open
caco3 wants to merge 2 commits into
garethgeorge:mainfrom
caco3:notification-hooks-add-test-function
Open

feat: add test hook button for notification hooks#1323
caco3 wants to merge 2 commits into
garethgeorge:mainfrom
caco3:notification-hooks-add-test-function

Conversation

@caco3

@caco3 caco3 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Adds a Test button to every hook form so users can immediately verify a hook (e.g. Telegram, Discord, Slack) with a test message before saving:

image

What's changed

  • Adds a TestHook RPC to the backend.
  • TestHook executes the provided hook with synthetic HookVars and returns an error if the hook fails.
  • TestHook uses a real TaskRunner via orchestrator.NewReadOnlyTaskRunner. A readOnlyTaskRunner decorator wraps the real runner and no-ops state-mutating methods (CreateOperation, UpdateOperation, DeleteOperation, ScheduleTask, LogrefWriter) so the test cannot create operations, schedule tasks, or write log refs.
  • Adds a play button to each hook form in the UI that serializes the current hook, calls testHook, and shows a success or error toast.

Technical details

  • proto/v1/service.proto: adds the TestHook RPC.
  • gen/go/v1/service.*.go: generated client/server code for the new RPC.
  • internal/orchestrator/taskrunnerimpl.go: adds readOnlyTaskRunner and the NewReadOnlyTaskRunner(orchestrator, task) constructor.
  • internal/api/backresthandler.go: implements TestHook by creating a minimal tasks.GenericOneoffTask and running the hook through orchestrator.NewReadOnlyTaskRunner.

Notes

  • This is mainly AI driven; my Go skills are very limited, but I reviewed and tested the Telegram hook.
  • I only tested the Telegram hook as I have no infrastructure for the other services.

@caco3
caco3 marked this pull request as ready for review July 24, 2026 21:37
@caco3

caco3 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

There are many open issues which will benefit from this feature:

Comment thread internal/api/backresthandler.go Outdated
}

ctx = orchestratorlogging.ContextWithWriter(ctx, io.Discard)
if err := handler.Execute(ctx, hook, vars, &testHookRunner{}, event); err != nil {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be fine to use a real [taskrunnerimpl](https://github.com/garethgeorge/backrest/blob/main/internal/orchestrator/taskrunnerimpl.go] here.

Let's introduce a ReadOnlyTaskRunnerDecorator in that same file which wraps a task runner and no-ops methods that mutate state

  1. CreateOperation
  2. DeleteOperation
  3. ScheduleTask
  4. LogrefWriter

wdyt?

@garethgeorge

Copy link
Copy Markdown
Owner

Thanks for the interest in contributing this -- agree this is much wanted and its a great contribution!

I think it'd actually be reasonable to inject a real but decorated taskrunnerimpl here and decorate it.

@caco3

caco3 commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

@garethgeorge Good call — using a real task runner is much cleaner. I added the ReadOnlyTaskRunnerDecorator and switched TestHook to use it.

What changed

internal/orchestrator/taskrunnerimpl.go

  • Added readOnlyTaskRunner, a decorator that wraps a real taskRunnerImpl.
  • It overrides the state-mutating methods to no-op:
    • CreateOperation
    • UpdateOperation
    • DeleteOperation
    • ScheduleTask
    • LogrefWriter
  • Every other method (Config, InstanceID, GetRepo, GetPlan, Logger, etc.) is delegated to the real runner.
  • Added NewReadOnlyTaskRunner(orchestrator, task) as a helper constructor.

internal/api/backresthandler.go

  • Replaced the hand-written testHookRunner stub with a real read-only runner:
    testTask := &tasks.GenericOneoffTask{
        BaseTask: tasks.BaseTask{
            TaskName: "test hook",
            TaskType: "test",
        },
        Do: func(ctx context.Context, st tasks.ScheduledTask, runner tasks.TaskRunner) error { return nil },
    }
    runner := orchestrator.NewReadOnlyTaskRunner(s.orchestrator, testTask)
  • Removed the testHookRunner and nopWriteCloser stub types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants