1// SPDX-License-Identifier: MIT
2
3package api_test
4
5import (
6 "errors"
7 "testing"
8
9 "connectrpc.com/connect"
10
11 "google.golang.org/protobuf/proto"
12 "google.golang.org/protobuf/reflect/protoreflect"
13 "google.golang.org/protobuf/types/known/fieldmaskpb"
14
15 "bnema.dev/ermite-api/gen/ermite"
16 "bnema.dev/ermite-api/gen/ermite/ermiteconnect"
17)
18
19// TestProcedures pins the procedure paths: they are the wire contract.
20func TestProcedures(t *testing.T) {
21 tests := []struct{ got, want string }{
22 {ermiteconnect.RepoServiceListReposProcedure, "/ermite.RepoService/ListRepos"},
23 {ermiteconnect.RepoServiceGetRepoProcedure, "/ermite.RepoService/GetRepo"},
24 {ermiteconnect.RepoServiceCreateRepoProcedure, "/ermite.RepoService/CreateRepo"},
25 {ermiteconnect.RepoServiceUpdateRepoProcedure, "/ermite.RepoService/UpdateRepo"},
26 {ermiteconnect.RepoServiceDeleteRepoProcedure, "/ermite.RepoService/DeleteRepo"},
27 {ermiteconnect.GitServiceListRefsProcedure, "/ermite.GitService/ListRefs"},
28 {ermiteconnect.GitServiceGetTreeProcedure, "/ermite.GitService/GetTree"},
29 {ermiteconnect.GitServiceListPathsProcedure, "/ermite.GitService/ListPaths"},
30 {ermiteconnect.GitServiceGetBlobProcedure, "/ermite.GitService/GetBlob"},
31 {ermiteconnect.GitServiceListCommitsProcedure, "/ermite.GitService/ListCommits"},
32 {ermiteconnect.GitServiceGetCommitProcedure, "/ermite.GitService/GetCommit"},
33 {ermiteconnect.GitServiceResolveRevisionProcedure, "/ermite.GitService/ResolveRevision"},
34 {ermiteconnect.TokenServiceCreateTokenProcedure, "/ermite.TokenService/CreateToken"},
35 {ermiteconnect.TokenServiceListTokensProcedure, "/ermite.TokenService/ListTokens"},
36 {ermiteconnect.TokenServiceRevokeTokenProcedure, "/ermite.TokenService/RevokeToken"},
37 {ermiteconnect.EventServiceWatchProcedure, "/ermite.EventService/Watch"},
38 {ermiteconnect.MergeRequestServiceCreateMergeRequestProcedure, "/ermite.MergeRequestService/CreateMergeRequest"},
39 {ermiteconnect.MergeRequestServiceListMergeRequestsProcedure, "/ermite.MergeRequestService/ListMergeRequests"},
40 {ermiteconnect.MergeRequestServiceGetMergeRequestProcedure, "/ermite.MergeRequestService/GetMergeRequest"},
41 {ermiteconnect.MergeRequestServiceAddCommentProcedure, "/ermite.MergeRequestService/AddComment"},
42 {ermiteconnect.MergeRequestServiceResolveCommentProcedure, "/ermite.MergeRequestService/ResolveComment"},
43 {ermiteconnect.MergeRequestServiceCloseMergeRequestProcedure, "/ermite.MergeRequestService/CloseMergeRequest"},
44 {ermiteconnect.MergeRequestServiceMergeMergeRequestProcedure, "/ermite.MergeRequestService/MergeMergeRequest"},
45 {ermiteconnect.MergeRequestServiceMarkReadProcedure, "/ermite.MergeRequestService/MarkRead"},
46 {ermiteconnect.SessionServiceCreateLoginLinkProcedure, "/ermite.SessionService/CreateLoginLink"},
47 {ermiteconnect.SessionServiceSignOutEverywhereProcedure, "/ermite.SessionService/SignOutEverywhere"},
48 {ermiteconnect.ServerServiceGetVersionProcedure, "/ermite.ServerService/GetVersion"},
49 {ermiteconnect.ServerServiceGetSigningKeyProcedure, "/ermite.ServerService/GetSigningKey"},
50 {ermiteconnect.RunnerServiceRegisterRunnerProcedure, "/ermite.RunnerService/RegisterRunner"},
51 {ermiteconnect.RunnerServiceFetchJobProcedure, "/ermite.RunnerService/FetchJob"},
52 {ermiteconnect.RunnerServiceUpdateJobProcedure, "/ermite.RunnerService/UpdateJob"},
53 {ermiteconnect.RunnerServiceAppendLogProcedure, "/ermite.RunnerService/AppendLog"},
54 {ermiteconnect.RunnerServiceHeartbeatProcedure, "/ermite.RunnerService/Heartbeat"},
55 {ermiteconnect.RunnerServiceWatchJobProcedure, "/ermite.RunnerService/WatchJob"},
56 {ermiteconnect.RunnerServiceCompleteJobProcedure, "/ermite.RunnerService/CompleteJob"},
57 {ermiteconnect.RunnerServiceResolveActionsProcedure, "/ermite.RunnerService/ResolveActions"},
58 {ermiteconnect.RunnerServiceUploadSummaryProcedure, "/ermite.RunnerService/UploadSummary"},
59 {ermiteconnect.RunServiceListRunsProcedure, "/ermite.RunService/ListRuns"},
60 {ermiteconnect.RunServiceGetRunProcedure, "/ermite.RunService/GetRun"},
61 {ermiteconnect.RunServiceGetJobProcedure, "/ermite.RunService/GetJob"},
62 {ermiteconnect.RunServiceGetJobLogProcedure, "/ermite.RunService/GetJobLog"},
63 {ermiteconnect.RunServiceListAnnotationsProcedure, "/ermite.RunService/ListAnnotations"},
64 {ermiteconnect.RunServiceGetStepSummariesProcedure, "/ermite.RunService/GetStepSummaries"},
65 {ermiteconnect.RunServiceCancelRunProcedure, "/ermite.RunService/CancelRun"},
66 {ermiteconnect.RunServiceRerunRunProcedure, "/ermite.RunService/RerunRun"},
67 {ermiteconnect.RunServiceListChecksProcedure, "/ermite.RunService/ListChecks"},
68 {ermiteconnect.RunServiceListRunnersProcedure, "/ermite.RunService/ListRunners"},
69 {ermiteconnect.RunServiceRemoveRunnerProcedure, "/ermite.RunService/RemoveRunner"},
70 }
71 for _, tt := range tests {
72 if tt.got != tt.want {
73 t.Errorf("procedure = %q, want %q", tt.got, tt.want)
74 }
75 }
76}
77
78// TestEnumNumbers pins enum numbers: they are part of the wire contract.
79func TestEnumNumbers(t *testing.T) {
80 if got := ermite.Scope_SCOPE_TOKEN_ADMIN; got != 9 {
81 t.Errorf("SCOPE_TOKEN_ADMIN = %d, want 9", got)
82 }
83 if got := ermite.FileStatus_FILE_STATUS_TYPE_CHANGED; got != 6 {
84 t.Errorf("FILE_STATUS_TYPE_CHANGED = %d, want 6", got)
85 }
86 if got := ermite.Visibility_VISIBILITY_PRIVATE; got != 2 {
87 t.Errorf("VISIBILITY_PRIVATE = %d, want 2", got)
88 }
89 if got := ermite.MergeMode_MERGE_MODE_SQUASH; got != 2 {
90 t.Errorf("MERGE_MODE_SQUASH = %d, want 2", got)
91 }
92 if got := ermite.MergeRequestState_MERGE_REQUEST_STATE_CLOSED; got != 3 {
93 t.Errorf("MERGE_REQUEST_STATE_CLOSED = %d, want 3", got)
94 }
95 if got := ermite.DiffSide_DIFF_SIDE_NEW; got != 2 {
96 t.Errorf("DIFF_SIDE_NEW = %d, want 2", got)
97 }
98 enums := []struct {
99 name string
100 got int32
101 want int32
102 }{
103 {"CI_STATUS_UNSPECIFIED", int32(ermite.CiStatus_CI_STATUS_UNSPECIFIED), 0},
104 {"CI_STATUS_QUEUED", int32(ermite.CiStatus_CI_STATUS_QUEUED), 1},
105 {"CI_STATUS_IN_PROGRESS", int32(ermite.CiStatus_CI_STATUS_IN_PROGRESS), 2},
106 {"CI_STATUS_COMPLETED", int32(ermite.CiStatus_CI_STATUS_COMPLETED), 3},
107 {"CI_CONCLUSION_UNSPECIFIED", int32(ermite.CiConclusion_CI_CONCLUSION_UNSPECIFIED), 0},
108 {"CI_CONCLUSION_SUCCESS", int32(ermite.CiConclusion_CI_CONCLUSION_SUCCESS), 1},
109 {"CI_CONCLUSION_FAILURE", int32(ermite.CiConclusion_CI_CONCLUSION_FAILURE), 2},
110 {"CI_CONCLUSION_CANCELLED", int32(ermite.CiConclusion_CI_CONCLUSION_CANCELLED), 3},
111 {"CI_CONCLUSION_SKIPPED", int32(ermite.CiConclusion_CI_CONCLUSION_SKIPPED), 4},
112 {"CI_CONCLUSION_TIMED_OUT", int32(ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT), 5},
113 {"CI_CONCLUSION_NEUTRAL", int32(ermite.CiConclusion_CI_CONCLUSION_NEUTRAL), 6},
114 {"CI_CONCLUSION_ACTION_REQUIRED", int32(ermite.CiConclusion_CI_CONCLUSION_ACTION_REQUIRED), 7},
115 {"STEP_STATE_UNSPECIFIED", int32(ermite.StepState_STEP_STATE_UNSPECIFIED), 0},
116 {"STEP_STATE_PENDING", int32(ermite.StepState_STEP_STATE_PENDING), 1},
117 {"STEP_STATE_IN_PROGRESS", int32(ermite.StepState_STEP_STATE_IN_PROGRESS), 2},
118 {"STEP_STATE_COMPLETED", int32(ermite.StepState_STEP_STATE_COMPLETED), 3},
119 {"RUNNER_ISOLATION_UNSPECIFIED", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_UNSPECIFIED), 0},
120 {"RUNNER_ISOLATION_VM", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_VM), 1},
121 {"RUNNER_ISOLATION_CONTAINER_SYSBOX", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_SYSBOX), 2},
122 {"RUNNER_ISOLATION_CONTAINER_PRIVILEGED", int32(ermite.RunnerIsolation_RUNNER_ISOLATION_CONTAINER_PRIVILEGED), 3},
123 {"CANCEL_REASON_UNSPECIFIED", int32(ermite.CancelReason_CANCEL_REASON_UNSPECIFIED), 0},
124 {"CANCEL_REASON_USER", int32(ermite.CancelReason_CANCEL_REASON_USER), 1},
125 {"CANCEL_REASON_FAIL_FAST", int32(ermite.CancelReason_CANCEL_REASON_FAIL_FAST), 2},
126 {"CANCEL_REASON_TIMEOUT", int32(ermite.CancelReason_CANCEL_REASON_TIMEOUT), 3},
127 {"ANNOTATION_LEVEL_UNSPECIFIED", int32(ermite.AnnotationLevel_ANNOTATION_LEVEL_UNSPECIFIED), 0},
128 {"ANNOTATION_LEVEL_NOTICE", int32(ermite.AnnotationLevel_ANNOTATION_LEVEL_NOTICE), 1},
129 {"ANNOTATION_LEVEL_WARNING", int32(ermite.AnnotationLevel_ANNOTATION_LEVEL_WARNING), 2},
130 {"ANNOTATION_LEVEL_FAILURE", int32(ermite.AnnotationLevel_ANNOTATION_LEVEL_FAILURE), 3},
131 {"ACTION_SOURCE_UNSPECIFIED", int32(ermite.ActionSource_ACTION_SOURCE_UNSPECIFIED), 0},
132 {"ACTION_SOURCE_GITHUB", int32(ermite.ActionSource_ACTION_SOURCE_GITHUB), 1},
133 {"ACTION_SOURCE_ERMITE", int32(ermite.ActionSource_ACTION_SOURCE_ERMITE), 2},
134 }
135 for _, e := range enums {
136 if e.got != e.want {
137 t.Errorf("%s = %d, want %d", e.name, e.got, e.want)
138 }
139 }
140}
141
142// TestEventRoundTrip checks that an event with a oneof kind survives the wire.
143func TestEventRoundTrip(t *testing.T) {
144 in := &ermite.WatchResponse{Event: &ermite.Event{
145 Id: "42",
146 Kind: &ermite.Event_Pushed{Pushed: &ermite.Pushed{
147 Repo: "ermite",
148 RefUpdates: []*ermite.RefUpdate{
149 {Ref: "refs/heads/main", OldId: "a", NewId: "b"},
150 },
151 }},
152 }}
153 b, err := proto.Marshal(in)
154 if err != nil {
155 t.Fatal(err)
156 }
157 var out ermite.WatchResponse
158 if err := proto.Unmarshal(b, &out); err != nil {
159 t.Fatal(err)
160 }
161 if !proto.Equal(in, &out) {
162 t.Errorf("round trip mismatch: %v != %v", &out, in)
163 }
164 if out.GetEvent().GetPushed().GetRefUpdates()[0].GetNewId() != "b" {
165 t.Errorf("unexpected ref update: %v", &out)
166 }
167}
168
169// TestUpdateRepoMask checks that the request carries a field mask.
170func TestUpdateRepoMask(t *testing.T) {
171 req := &ermite.UpdateRepoRequest{
172 Repo: "ermite",
173 Visibility: ermite.Visibility_VISIBILITY_PUBLIC,
174 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"visibility"}},
175 }
176 if got := req.GetUpdateMask().GetPaths(); len(got) != 1 || got[0] != "visibility" {
177 t.Errorf("paths = %v", got)
178 }
179}
180
181// TestMergeRequestEventRoundTrip checks the merge request event kinds and the
182// repository settings survive the wire.
183func TestMergeRequestEventRoundTrip(t *testing.T) {
184 in := &ermite.WatchResponse{Event: &ermite.Event{
185 Id: "43",
186 Kind: &ermite.Event_CommentAdded{CommentAdded: &ermite.CommentAdded{
187 Repo: "ermite",
188 Number: 7,
189 Comment: &ermite.Comment{
190 Id: "c1", Path: "a.go", Line: 3, Side: ermite.DiffSide_DIFF_SIDE_OLD, Text: "hi",
191 },
192 }},
193 }}
194 b, err := proto.Marshal(in)
195 if err != nil {
196 t.Fatal(err)
197 }
198 var out ermite.WatchResponse
199 if err := proto.Unmarshal(b, &out); err != nil {
200 t.Fatal(err)
201 }
202 if !proto.Equal(in, &out) {
203 t.Errorf("round trip mismatch: %v != %v", &out, in)
204 }
205 if got := out.GetEvent().GetCommentAdded().GetNumber(); got != 7 {
206 t.Errorf("number = %d, want 7", got)
207 }
208
209 req := &ermite.UpdateRepoRequest{
210 Repo: "ermite",
211 Settings: &ermite.RepoSettings{
212 AllowedMergeModes: []ermite.MergeMode{ermite.MergeMode_MERGE_MODE_SQUASH},
213 FailingChecksBlock: true,
214 },
215 UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"settings.allowed_merge_modes", "settings.failing_checks_block"}},
216 }
217 if got := req.GetSettings().GetAllowedMergeModes(); len(got) != 1 || got[0] != ermite.MergeMode_MERGE_MODE_SQUASH {
218 t.Errorf("modes = %v", got)
219 }
220}
221
222// TestCIEventRoundTrip checks that the CI event kinds keep their field numbers
223// and survive the wire.
224func TestCIEventRoundTrip(t *testing.T) {
225 in := &ermite.WatchResponse{Event: &ermite.Event{
226 Id: "44",
227 Kind: &ermite.Event_JobUpdated{JobUpdated: &ermite.JobUpdated{Job: &ermite.RunJob{
228 Id: "j1",
229 RunId: 9,
230 Attempt: 2,
231 Status: ermite.CiStatus_CI_STATUS_COMPLETED,
232 Conclusion: ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT,
233 Isolation: ermite.RunnerIsolation_RUNNER_ISOLATION_VM,
234 Steps: []*ermite.Step{{Number: 1, Name: "Set up job", LogStart: 0, LogEnd: 3}},
235 }}},
236 }}
237 b, err := proto.Marshal(in)
238 if err != nil {
239 t.Fatal(err)
240 }
241 var out ermite.WatchResponse
242 if err := proto.Unmarshal(b, &out); err != nil {
243 t.Fatal(err)
244 }
245 if !proto.Equal(in, &out) {
246 t.Errorf("round trip mismatch: %v != %v", &out, in)
247 }
248 f := (&ermite.Event{}).ProtoReflect().Descriptor().Fields()
249 for name, num := range map[string]int{"run_updated": 12, "job_updated": 13} {
250 if fd := f.ByName(protoreflect.Name(name)); fd == nil || int(fd.Number()) != num {
251 t.Errorf("event field %s: %v, want number %d", name, fd, num)
252 }
253 }
254}
255
256// TestJobRoundTrip checks the job handed to a runner survives the wire.
257func TestJobRoundTrip(t *testing.T) {
258 in := &ermite.FetchJobResponse{Job: &ermite.Job{
259 JobId: "j1", RunId: 9, Attempt: 1, JobToken: "tok", WorkflowPath: ".github/workflows/ci.yml",
260 WorkflowYaml: "on: push", JobKey: "build", Matrix: "{}", Contexts: "{}",
261 Labels: []string{"self-hosted"}, TimeoutMinutes: 360, Repo: "ermite",
262 Sha: "0123456789012345678901234567890123456789", Ref: "refs/heads/main",
263 }}
264 b, err := proto.Marshal(in)
265 if err != nil {
266 t.Fatal(err)
267 }
268 var out ermite.FetchJobResponse
269 if err := proto.Unmarshal(b, &out); err != nil {
270 t.Fatal(err)
271 }
272 if !proto.Equal(in, &out) {
273 t.Errorf("round trip mismatch: %v != %v", &out, in)
274 }
275}
276
277// TestWatchJobRoundTrip checks the three kinds of WatchJob message and the
278// JobEnded detail.
279func TestWatchJobRoundTrip(t *testing.T) {
280 msgs := []*ermite.WatchJobResponse{
281 {Msg: &ermite.WatchJobResponse_Keepalive{Keepalive: &ermite.Keepalive{}}},
282 {Msg: &ermite.WatchJobResponse_Cancel{Cancel: &ermite.CancelRequest{
283 Reason: ermite.CancelReason_CANCEL_REASON_FAIL_FAST, Message: "m",
284 }}},
285 {Msg: &ermite.WatchJobResponse_Ended{Ended: &ermite.JobEnded{
286 Conclusion: ermite.CiConclusion_CI_CONCLUSION_CANCELLED, Error: "e",
287 }}},
288 }
289 for _, in := range msgs {
290 b, err := proto.Marshal(in)
291 if err != nil {
292 t.Fatal(err)
293 }
294 var out ermite.WatchJobResponse
295 if err := proto.Unmarshal(b, &out); err != nil {
296 t.Fatal(err)
297 }
298 if !proto.Equal(in, &out) {
299 t.Errorf("round trip mismatch: %v != %v", &out, in)
300 }
301 }
302 // The detail travels in a Connect error.
303 d, err := connect.NewErrorDetail(&ermite.JobEnded{Conclusion: ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT})
304 if err != nil {
305 t.Fatal(err)
306 }
307 cerr := connect.NewError(connect.CodeFailedPrecondition, errors.New("job ended"))
308 cerr.AddDetail(d)
309 v, err := cerr.Details()[0].Value()
310 if err != nil {
311 t.Fatal(err)
312 }
313 if got := v.(*ermite.JobEnded).GetConclusion(); got != ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT {
314 t.Errorf("detail conclusion = %v", got)
315 }
316}
317
318// TestCompleteJobRoundTrip checks the final report of a job and its answer.
319func TestCompleteJobRoundTrip(t *testing.T) {
320 in := &ermite.CompleteJobRequest{
321 JobId: "j1",
322 Conclusion: ermite.CiConclusion_CI_CONCLUSION_CANCELLED,
323 Outputs: map[string]string{"a": "b"},
324 RunnerShutdown: true,
325 Steps: []*ermite.StepUpdate{{Number: 2, State: ermite.StepState_STEP_STATE_COMPLETED}},
326 }
327 b, err := proto.Marshal(in)
328 if err != nil {
329 t.Fatal(err)
330 }
331 var out ermite.CompleteJobRequest
332 if err := proto.Unmarshal(b, &out); err != nil {
333 t.Fatal(err)
334 }
335 if !proto.Equal(in, &out) {
336 t.Errorf("round trip mismatch: %v != %v", &out, in)
337 }
338 r := &ermite.CompleteJobResponse{Applied: false, Conclusion: ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT}
339 if r.GetApplied() || r.GetConclusion() != ermite.CiConclusion_CI_CONCLUSION_TIMED_OUT {
340 t.Errorf("unexpected response %v", r)
341 }
342}
343
344// TestRunNameField pins the number of Run.name, the GitHub name of the run
345// that sits next to workflow_name, and that it survives the wire.
346func TestRunNameField(t *testing.T) {
347 fd := (&ermite.Run{}).ProtoReflect().Descriptor().Fields().ByName("name")
348 if fd == nil || fd.Number() != 19 {
349 t.Fatalf("Run.name = %v, want field 19", fd)
350 }
351 in := &ermite.Run{WorkflowName: "Names Workflow", Name: "run push on branch by someone", Title: "run push on branch by someone"}
352 b, err := proto.Marshal(in)
353 if err != nil {
354 t.Fatal(err)
355 }
356 var out ermite.Run
357 if err := proto.Unmarshal(b, &out); err != nil {
358 t.Fatal(err)
359 }
360 if out.GetName() != in.GetName() || out.GetWorkflowName() != "Names Workflow" {
361 t.Errorf("round trip: name %q, workflow_name %q", out.GetName(), out.GetWorkflowName())
362 }
363}
364
365// TestCI2FieldNumbers pins the numbers of the fields added for CI-2.
366func TestCI2FieldNumbers(t *testing.T) {
367 tests := []struct {
368 msg proto.Message
369 fields map[string]int
370 }{
371 {&ermite.Job{}, map[string]int{"github_token": 15}},
372 {&ermite.UpdateJobRequest{}, map[string]int{"annotations": 3}},
373 {&ermite.CompleteJobRequest{}, map[string]int{"annotations": 6}},
374 {&ermite.StepUpdate{}, map[string]int{"outcome": 7}},
375 {&ermite.Step{}, map[string]int{"outcome": 9}},
376 {&ermite.Annotation{}, map[string]int{
377 "level": 1, "title": 2, "message": 3, "file": 4, "start_line": 5,
378 "end_line": 6, "start_column": 7, "end_column": 8, "step_number": 9,
379 }},
380 {&ermite.ActionRef{}, map[string]int{"uses": 1}},
381 {&ermite.ResolveActionsRequest{}, map[string]int{"job_id": 1, "refs": 2}},
382 {&ermite.ResolveActionsResponse{}, map[string]int{"actions": 1}},
383 {&ermite.ResolvedAction{}, map[string]int{
384 "ref": 1, "name_with_owner": 2, "path": 3, "resolved_sha": 4,
385 "tarball_url": 5, "token": 6, "source": 7, "error": 8,
386 }},
387 {&ermite.UploadSummaryRequest{}, map[string]int{"job_id": 1, "step_number": 2, "markdown": 3}},
388 {&ermite.ListAnnotationsRequest{}, map[string]int{"repo": 1, "run_id": 2, "attempt": 3, "job_id": 4}},
389 {&ermite.ListAnnotationsResponse{}, map[string]int{"annotations": 1, "truncated": 2}},
390 {&ermite.JobAnnotation{}, map[string]int{"job_id": 1, "annotation": 2}},
391 {&ermite.GetStepSummariesRequest{}, map[string]int{"repo": 1, "job_id": 2, "attempt": 3}},
392 {&ermite.GetStepSummariesResponse{}, map[string]int{"summaries": 1, "truncated": 2}},
393 {&ermite.StepSummary{}, map[string]int{"step_number": 1, "markdown": 2}},
394 }
395 for _, tt := range tests {
396 md := tt.msg.ProtoReflect().Descriptor()
397 for name, num := range tt.fields {
398 if fd := md.Fields().ByName(protoreflect.Name(name)); fd == nil || int(fd.Number()) != num {
399 t.Errorf("%s.%s: %v, want number %d", md.Name(), name, fd, num)
400 }
401 }
402 }
403}
404
405// TestCI2RoundTrip checks that the CI-2 messages survive the wire.
406func TestCI2RoundTrip(t *testing.T) {
407 msgs := []proto.Message{
408 &ermite.Job{JobId: "j1", GithubToken: "ermitegh_x"},
409 &ermite.UpdateJobRequest{
410 JobId: "j1",
411 Steps: []*ermite.StepUpdate{{
412 Number: 2, State: ermite.StepState_STEP_STATE_COMPLETED,
413 Conclusion: ermite.CiConclusion_CI_CONCLUSION_SUCCESS,
414 Outcome: ermite.CiConclusion_CI_CONCLUSION_FAILURE,
415 }},
416 Annotations: []*ermite.Annotation{{
417 Level: ermite.AnnotationLevel_ANNOTATION_LEVEL_WARNING, Title: "t", Message: "m",
418 File: "a.go", StartLine: 1, EndLine: 2, StartColumn: 3, EndColumn: 4, StepNumber: 2,
419 }},
420 },
421 &ermite.CompleteJobRequest{
422 JobId: "j1",
423 Conclusion: ermite.CiConclusion_CI_CONCLUSION_SUCCESS,
424 Annotations: []*ermite.Annotation{{Level: ermite.AnnotationLevel_ANNOTATION_LEVEL_FAILURE, Message: "m", StepNumber: 3}},
425 },
426 &ermite.ResolveActionsRequest{JobId: "j1", Refs: []*ermite.ActionRef{{Uses: "actions/checkout@v4"}, {Uses: "o/r/p@main"}}},
427 &ermite.ResolveActionsResponse{Actions: []*ermite.ResolvedAction{
428 {
429 Ref: "o/r/p@main", NameWithOwner: "o/r", Path: "p", ResolvedSha: "0123456789012345678901234567890123456789",
430 TarballUrl: "https://example.test/o/r.tar.gz", Token: "t", Source: ermite.ActionSource_ACTION_SOURCE_ERMITE,
431 },
432 {Ref: "x/y@z", Error: "Unable to resolve action `x/y@z`, unable to find version `z`"},
433 }},
434 &ermite.UploadSummaryRequest{JobId: "j1", StepNumber: 2, Markdown: "# hi"},
435 &ermite.ListAnnotationsResponse{Annotations: []*ermite.JobAnnotation{{JobId: "j1", Annotation: &ermite.Annotation{Message: "m"}}}, Truncated: true},
436 &ermite.GetStepSummariesResponse{Summaries: []*ermite.StepSummary{{StepNumber: 2, Markdown: "# hi"}}, Truncated: true},
437 &ermite.RunJob{Id: "j1", Steps: []*ermite.Step{{Number: 2, Outcome: ermite.CiConclusion_CI_CONCLUSION_FAILURE}}},
438 }
439 for _, in := range msgs {
440 b, err := proto.Marshal(in)
441 if err != nil {
442 t.Fatal(err)
443 }
444 out := in.ProtoReflect().New().Interface()
445 if err := proto.Unmarshal(b, out); err != nil {
446 t.Fatal(err)
447 }
448 if !proto.Equal(in, out) {
449 t.Errorf("%T: round trip mismatch: %v != %v", in, out, in)
450 }
451 }
452}