Skip to content

Commit 29258bd

Browse files
committed
make Action a functor that consumes GH and Slack api modules (fixes tests)
Auxiliary cli commands have been reenabled. The tests, as well as `check_gh_action` and `check_slack_action`, now rely on the functor-based Action module.
1 parent 0bb06b3 commit 29258bd

File tree

5 files changed

+104
-109
lines changed

5 files changed

+104
-109
lines changed

lib/action.ml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ let action_error msg = raise (Action_error msg)
1212

1313
let log = Log.from "action"
1414

15-
module Github_api = Api_remote.Github
16-
module Slack_api = Api_remote.Slack
17-
18-
module Action = struct
15+
module Action (Github_api : Api.Github) (Slack_api : Api.Slack) = struct
1916
let partition_push cfg n =
2017
let default = Option.to_list cfg.prefix_rules.default_channel in
2118
let rules = cfg.prefix_rules.rules in

src/notabot.ml

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -25,59 +25,48 @@ let http_server_action addr port config secrets state =
2525
)
2626
)
2727

28-
(* let check_slack_action webhook file =
28+
(** In check mode, instead of actually sending the message to slack, we
29+
simply print it in the console *)
30+
let check_gh_action file json config secrets state =
31+
Lwt_main.run
32+
begin
33+
match Github.event_of_filename file with
34+
| None ->
35+
log#error "aborting because payload %s is not named properly, named should be KIND.NAME_OF_PAYLOAD.json" file;
36+
Lwt.return_unit
37+
| Some kind ->
38+
let headers = [ "x-github-event", kind ] in
39+
( match%lwt Common.get_local_file file with
40+
| Error e ->
41+
log#error "%s" e;
42+
Lwt.return_unit
43+
| Ok body ->
44+
let ctx = Context.make ~config_filename:config ~secrets_filepath:secrets ?state_filepath:state () in
45+
let%lwt () =
46+
if json then
47+
let module Action = Action.Action (Api_remote.Github) (Api_local.Slack_json) in
48+
Action.process_github_notification ctx headers body
49+
else
50+
let module Action = Action.Action (Api_remote.Github) (Api_local.Slack_simple) in
51+
Action.process_github_notification ctx headers body
52+
in
53+
Lwt.return_unit
54+
)
55+
end
56+
57+
let check_slack_action url file =
2958
let data = Stdio.In_channel.read_all file in
59+
let chan = Printf.sprintf "webhook %s" url in
3060
match Slack_j.webhook_notification_of_string data with
3161
| exception exn -> log#error ~exn "unable to parse notification"
32-
| _ -> Lwt_main.run (Slack.send_notification webhook data)
33-
34-
let check_common file print config secrets state_path =
35-
let ctx_thunk =
36-
Context.make_thunk ~state_path ~cfg_path_or_remote_filename:config ~secrets_path:secrets ~cfg_action_after_refresh
37-
()
38-
in
39-
let filename = Caml.Filename.basename file in
40-
match Github.event_of_filename filename with
41-
| None ->
42-
log#error "aborting because payload %s is not named properly, named should be KIND.NAME_OF_PAYLOAD.json" file;
43-
Lwt.return_unit
44-
| Some kind ->
45-
let headers = [ "x-github-event", kind ] in
46-
(* read the event from a file and try to parse it *)
47-
( match Github.parse_exn ~secret:None headers (Stdio.In_channel.read_all file) with
48-
| exception exn ->
49-
log#error ~exn "unable to parse payload";
50-
Lwt.return_unit
51-
| event ->
52-
let%lwt ctx = Context.resolve_ctx_in_thunk ctx_thunk event in
53-
let%lwt notifs = Action.generate_notifications ctx event in
54-
List.iter ~f:print notifs;
55-
Lwt.return_unit
56-
)
57-
58-
let print_simplified_message (chan, msg) =
59-
(* In check mode, instead of actually sending the message to slack, we
60-
simply print it in the console *)
61-
log#info "will notify %s%s" chan
62-
( match msg.Slack_t.text with
63-
| None -> ""
64-
| Some s -> Printf.sprintf " with %S" s
65-
)
66-
67-
let print_json_message (chan, msg) =
68-
let json = Slack_j.string_of_webhook_notification msg in
69-
log#info "will notify %s" chan;
70-
let url = Uri.of_string "https://api.slack.com/docs/messages/builder" in
71-
let url = Uri.add_query_param url ("msg", [ json ]) in
72-
log#info "%s" (Uri.to_string url);
73-
log#info "%s" json
74-
75-
let check_gh_action file json config secrets state =
76-
Lwt_main.run
77-
( match json with
78-
| false -> check_common file print_simplified_message config secrets state
79-
| true -> check_common file print_json_message config secrets state
80-
) *)
62+
| msg ->
63+
Lwt_main.run
64+
( match%lwt Api_remote.Slack.send_notification ~chan ~msg ~url with
65+
| Error e ->
66+
log#error "%s" e;
67+
Lwt.return_unit
68+
| Ok () -> Lwt.return_unit
69+
)
8170

8271
(* flags *)
8372

@@ -91,7 +80,7 @@ let port =
9180

9281
let config =
9382
let doc = "remote configuration file name" in
94-
Arg.(value & opt file "monorobot.json" & info [ "config" ] ~docv:"CONFIG" ~doc)
83+
Arg.(value & opt string "monorobot.json" & info [ "config" ] ~docv:"CONFIG" ~doc)
9584

9685
let secrets =
9786
let doc = "configuration file containing secrets" in
@@ -125,7 +114,7 @@ let run =
125114
let term = Term.(const http_server_action $ addr $ port $ config $ secrets $ state) in
126115
term, info
127116

128-
(* let check_gh =
117+
let check_gh =
129118
let doc = "read a Github notification from a file and display the actions that will be taken; used for testing" in
130119
let info = Term.info "check_gh" ~doc in
131120
let term = Term.(const check_gh_action $ gh_payload $ json $ config $ secrets $ state) in
@@ -135,13 +124,12 @@ let check_slack =
135124
let doc = "read a Slack notification from a file and send it to a webhook; used for testing" in
136125
let info = Term.info "check_slack" ~doc in
137126
let term = Term.(const check_slack_action $ slack_webhook_url $ slack_payload) in
138-
term, info *)
127+
term, info
139128

140129
let default_cmd =
141130
let doc = "the notification bot" in
142131
Term.(ret (const (`Help (`Pager, None)))), Term.info "monorobot" ~doc
143132

144-
(* let cmds = [ run; check_gh; check_slack ] *)
145-
let cmds = [ run ]
133+
let cmds = [ run; check_gh; check_slack ]
146134

147135
let () = Term.(exit @@ eval_choice default_cmd cmds)

src/request_handler.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
open Printf
22
open Devkit
33
open Lib
4-
open Action
54

65
let log = Log.from "request_handler"
76

7+
module Action = Action.Action (Api_remote.Github) (Api_remote.Slack)
8+
89
let setup_http ~ctx ~signature ~port ~ip =
910
let open Httpev in
1011
let connection = Unix.ADDR_INET (ip, port) in

test/notabot.json

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{
2-
"offline": "github-api-cache",
32
"main_branch_name": "develop",
43
"status_rules": {
54
"allowed_pipelines": [
@@ -14,34 +13,29 @@
1413
"success": "once"
1514
}
1615
},
17-
"prefix_rules": {
16+
"prefix_rules": {
1817
"default_channel": "default",
1918
"rules": [
2019
{
2120
"allow": [
2221
"backend/a1"
2322
],
24-
"ignore": [],
2523
"channel": "a1"
2624
},
2725
{
2826
"allow": [
2927
"backend/a1/longest"
3028
],
31-
"ignore": [],
3229
"channel": "longest-a1"
3330
},
3431
{
3532
"allow": [
3633
"backend/a5",
3734
"backend/a4"
3835
],
39-
"ignore": [],
4036
"channel": "backend"
4137
},
4238
{
43-
"allow": [],
44-
"ignore": [],
4539
"channel": "all-push-events"
4640
}
4741
]
@@ -53,25 +47,21 @@
5347
"allow": [
5448
"backend"
5549
],
56-
"ignore": [],
5750
"channel": "backend"
5851
},
5952
{
6053
"allow": [
6154
"a1"
6255
],
63-
"ignore": [],
6456
"channel": "a1-bot"
6557
},
6658
{
6759
"allow": [
6860
"a3"
6961
],
70-
"ignore": [],
7162
"channel": "a3"
7263
},
7364
{
74-
"allow": [],
7565
"ignore": [
7666
"backend",
7767
"a1",

test/test.ml

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,66 @@
11
open Base
22
open Lib
3-
open Action
43

54
let log = Devkit.Log.from "test"
65

7-
let print_notif (chan, msg) =
8-
let json =
9-
msg |> Slack_j.string_of_webhook_notification |> Yojson.Basic.from_string |> Yojson.Basic.pretty_to_string
10-
in
11-
Stdio.printf "will notify #%s\n" chan;
12-
Stdio.printf "%s\n" json
6+
let mock_payload_dir = Caml.Filename.concat Caml.Filename.parent_dir_name "mock_payloads"
137

14-
let process ~state_dir ~cfg_path ~secrets_path file =
15-
Stdio.printf "===== file %s =====\n" file;
16-
let filename = Caml.Filename.basename file in
17-
match Github.event_of_filename filename with
18-
| None -> Lwt.return_unit
19-
| Some kind ->
20-
let headers = [ "x-github-event", kind ] in
21-
( match Github.parse_exn ~secret:None headers (Stdio.In_channel.read_all file) with
22-
| exception exn ->
23-
Stdio.printf "exception when parsing %s: %s\n" file (Exn.to_string exn);
24-
Lwt.return_unit
25-
| event ->
26-
Devkit.Log.set_loglevels "error";
27-
let state_path = Caml.Filename.concat state_dir @@ Caml.Filename.basename file in
28-
let ctx_partial = Context.make ~state_path ~secrets_path ~disable_write:true in
29-
let%lwt ctx =
30-
try%lwt ctx_partial ~cfg_args:(RemoteMake (cfg_path, event)) ()
31-
with exn ->
32-
log#info ~exn "unable to find a remote configuration %s" cfg_path;
33-
ctx_partial ~cfg_args:(LocalMake cfg_path) ()
34-
in
35-
let%lwt notifs = Action.generate_notifications ctx event in
36-
List.iter notifs ~f:print_notif;
37-
Lwt.return_unit
38-
)
8+
let mock_state_dir = Caml.Filename.concat Caml.Filename.parent_dir_name "mock_states"
9+
10+
module Action_local = Action.Action (Api_local.Github) (Api_local.Slack)
11+
12+
let get_mock_payloads () =
13+
let files = Caml.Sys.readdir mock_payload_dir in
14+
Array.sort files ~compare:String.compare;
15+
Array.to_list files
16+
|> List.filter_map ~f:(fun fn -> Github.event_of_filename fn |> Option.map ~f:(fun kind -> kind, fn))
17+
|> List.map ~f:(fun (kind, fn) ->
18+
let payload_path = Caml.Filename.concat mock_payload_dir fn in
19+
let state_path = Caml.Filename.concat mock_state_dir fn in
20+
if Caml.Sys.file_exists state_path then kind, payload_path, Some state_path else kind, payload_path, None)
21+
22+
let process ~(ctx : Context.t) (kind, path, state_path) =
23+
let%lwt ctx =
24+
match state_path with
25+
| None -> Lwt.return { ctx with state = State.empty }
26+
| Some state_path ->
27+
( match%lwt Common.get_local_file state_path with
28+
| Error e ->
29+
log#error "failed to read %s: %s" state_path e;
30+
Lwt.return ctx
31+
| Ok file ->
32+
let state = State_j.state_of_string file in
33+
Lwt.return { ctx with state }
34+
)
35+
in
36+
Stdio.printf "===== file %s =====\n" path;
37+
let headers = [ "x-github-event", kind ] in
38+
match%lwt Common.get_local_file path with
39+
| Error e ->
40+
log#error "failed to read %s: %s" path e;
41+
Lwt.return_unit
42+
| Ok event ->
43+
let%lwt _ctx = Action_local.process_github_notification ctx headers event in
44+
Lwt.return_unit
3945

4046
let () =
41-
let mock_dir = "../mock_payloads" in
42-
let jsons = Caml.Sys.readdir mock_dir in
43-
let jsons = Array.map ~f:(fun p -> Caml.Filename.concat mock_dir p) jsons in
44-
Array.sort jsons ~compare:String.compare;
47+
let payloads = get_mock_payloads () in
48+
let repo : Github_t.repository = { name = ""; full_name = ""; url = ""; commits_url = ""; contents_url = "" } in
49+
let ctx = Context.make ~state_filepath:"state.json" () in
4550
Lwt_main.run
46-
(let jsons = Array.to_list jsons in
47-
Lwt_list.iter_s (process ~state_dir:"../mock_states" ~cfg_path:"notabot.json" ~secrets_path:"secrets.json") jsons)
51+
( match%lwt Api_local.Github.get_config ~ctx ~repo with
52+
| Error e ->
53+
log#error "%s" e;
54+
Lwt.return_unit
55+
| Ok config ->
56+
(* can remove this wrapper once status_rules doesn't depend on Config.t *)
57+
let config = Config.make config in
58+
let ctx = { ctx with config = Some config } in
59+
( match%lwt Context.refresh_secrets ctx with
60+
| Ok ctx -> Lwt_list.iter_s (process ~ctx) payloads
61+
| Error e ->
62+
log#error "failed to read secrets:";
63+
log#error "%s" e;
64+
Lwt.return_unit
65+
)
66+
)

0 commit comments

Comments
 (0)