Skip to content
This repository was archived by the owner on Dec 6, 2025. It is now read-only.

Commit 40b3137

Browse files
committed
unassign
1 parent ecd16aa commit 40b3137

File tree

5 files changed

+153
-12
lines changed

5 files changed

+153
-12
lines changed

gen/go/libs/common/v1/conflict.pb.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/libs/common/v1/conflict.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ message Conflict {
2222

2323
message AttributeConflict {
2424
google.protobuf.Any is = 1; // CAUTION: may be missing, if the is underlying value is missing (e.g., unassigned beds)
25-
google.protobuf.Any want = 2;
25+
google.protobuf.Any want = 2; // CAUTION: may be missing, if the requested value is missing (e.g., unassignment of a bed)
2626
}

services/tasks-svc/internal/patient/api/grpc.go

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -545,9 +545,49 @@ func (s *PatientGrpcService) UnassignBed(ctx context.Context, req *pb.UnassignBe
545545
return nil, err
546546
}
547547

548-
consistency, err := s.handlers.Commands.V1.UnassignBed(ctx, patientID)
549-
if err != nil {
550-
return nil, err
548+
expConsistency, ok := hwutil.ParseConsistency(req.Consistency)
549+
if !ok {
550+
return nil, common.UnparsableConsistencyError(ctx, "consistency")
551+
}
552+
553+
var consistency uint64
554+
555+
for i := 0; true; i++ {
556+
if i > 10 {
557+
log.Warn().Msg("AssignBed: conflict circuit breaker triggered")
558+
return nil, fmt.Errorf("failed conflict resolution")
559+
}
560+
561+
c, conflict, err := s.handlers.Commands.V1.UnassignBed(ctx, patientID, expConsistency)
562+
if err != nil {
563+
return nil, err
564+
}
565+
consistency = c
566+
if conflict == nil {
567+
break
568+
}
569+
conflicts := make(map[string]*commonpb.AttributeConflict)
570+
571+
// TODO: find a generic approach
572+
if conflict.Is.BedID.Valid && conflict.Was.BedID != conflict.Is.BedID {
573+
conflicts["bed_id"], err = util.AttributeConflict(
574+
wrapperspb.String(conflict.Is.BedID.UUID.String()),
575+
nil,
576+
)
577+
if err != nil {
578+
return nil, err
579+
}
580+
}
581+
582+
if len(conflicts) != 0 {
583+
return &pb.UnassignBedResponse{
584+
Conflict: &commonpb.Conflict{ConflictingAttributes: conflicts},
585+
Consistency: strconv.FormatUint(conflict.Consistency, 10),
586+
}, nil
587+
}
588+
589+
// no conflict? retry with new consistency
590+
expConsistency = &conflict.Consistency
551591
}
552592

553593
log.Info().Str("patientID", patientID.String()).Msg("unassigned bed from patient")

services/tasks-svc/internal/patient/commands/v1/unassign_bed.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,37 @@ import (
55
"github.com/google/uuid"
66
"hwes"
77
"tasks-svc/internal/patient/aggregate"
8+
"tasks-svc/internal/patient/models"
89
)
910

10-
type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID) (uint64, error)
11+
type UnassignBedConflict struct {
12+
Consistency uint64
13+
Was *models.Patient
14+
Is *models.Patient
15+
}
16+
17+
type UnassignBedCommandHandler func(ctx context.Context, patientID uuid.UUID, expectedConsistency *uint64) (uint64, *UnassignBedConflict, error)
1118

1219
func NewUnassignBedCommandHandler(as hwes.AggregateStore) UnassignBedCommandHandler {
13-
return func(ctx context.Context, patientID uuid.UUID) (uint64, error) {
14-
a, err := aggregate.LoadPatientAggregate(ctx, as, patientID)
20+
return func(ctx context.Context, patientID uuid.UUID, expectedConsistency *uint64) (uint64, *UnassignBedConflict, error) {
21+
a, oldState, err := aggregate.LoadPatientAggregateWithSnapshotAt(ctx, as, patientID, expectedConsistency)
1522
if err != nil {
16-
return 0, err
23+
return 0, nil, err
24+
}
25+
26+
// update has happened since
27+
if expectedConsistency != nil && *expectedConsistency != a.GetVersion() {
28+
return 0, &UnassignBedConflict{
29+
Consistency: a.GetVersion(),
30+
Was: oldState,
31+
Is: a.Patient,
32+
}, nil
1733
}
1834

1935
if err := a.UnassignBed(ctx); err != nil {
20-
return 0, err
36+
return 0, nil, err
2137
}
22-
return as.Save(ctx, a)
38+
consistency, err := as.Save(ctx, a)
39+
return consistency, nil, err
2340
}
2441
}

services/tasks-svc/stories/PatientCRUD_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,3 +838,87 @@ func TestAssignBedConflict(t *testing.T) {
838838
})
839839
}
840840
}
841+
842+
func TestUnassignBedConflict(t *testing.T) {
843+
ctx := context.Background()
844+
patientClient := patientServiceClient()
845+
846+
wardId, _ := prepareWard(t, ctx, "")
847+
roomId, _ := prepareRoom(t, ctx, wardId, "")
848+
849+
A, _ := prepareBed(t, ctx, roomId, "A")
850+
B, _ := prepareBed(t, ctx, roomId, "B")
851+
852+
testMatrix := []struct {
853+
was string
854+
is *string
855+
expectConflict bool
856+
}{
857+
{A, &B, true},
858+
{A, &A, false},
859+
{A, nil, false},
860+
}
861+
862+
for i, o := range testMatrix {
863+
t.Run(t.Name()+"_"+strconv.Itoa(i), func(t *testing.T) {
864+
// WAS
865+
patientRes, err := patientClient.CreatePatient(ctx, &pb.CreatePatientRequest{
866+
HumanReadableIdentifier: t.Name(),
867+
Notes: hwutil.PtrTo("A patient for test " + t.Name()),
868+
})
869+
assert.NoError(t, err)
870+
time.Sleep(time.Millisecond * 100)
871+
872+
initialAssignment, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{
873+
Id: patientRes.Id,
874+
BedId: o.was,
875+
Consistency: &patientRes.Consistency,
876+
})
877+
assert.NoError(t, err)
878+
assert.Nil(t, initialAssignment.Conflict)
879+
880+
id := patientRes.Id
881+
initialConsistency := initialAssignment.Consistency
882+
883+
time.Sleep(time.Millisecond * 100)
884+
885+
// IS
886+
if o.is != nil {
887+
a, err := patientClient.AssignBed(ctx, &pb.AssignBedRequest{
888+
Id: id,
889+
BedId: *o.is,
890+
Consistency: &initialConsistency,
891+
})
892+
assert.NoError(t, err)
893+
assert.Nil(t, a.Conflict)
894+
} else {
895+
u, err := patientClient.UnassignBed(ctx, &pb.UnassignBedRequest{
896+
Id: id,
897+
Consistency: &initialConsistency,
898+
})
899+
assert.NoError(t, err)
900+
assert.Nil(t, u.Conflict)
901+
}
902+
time.Sleep(time.Millisecond * 100)
903+
904+
// WANT
905+
updateRes, err := patientClient.UnassignBed(ctx, &pb.UnassignBedRequest{
906+
Id: id,
907+
Consistency: &initialConsistency,
908+
})
909+
assert.NoError(t, err)
910+
911+
// EXPECT
912+
assert.Equal(t, o.expectConflict, updateRes.Conflict != nil)
913+
if o.expectConflict {
914+
conflict := updateRes.Conflict.ConflictingAttributes["bed_id"]
915+
assert.NotNil(t, conflict)
916+
exp := ""
917+
if o.is != nil {
918+
exp = "is:{[type.googleapis.com/google.protobuf.StringValue]:{value:\"" + *o.is + "\"}}"
919+
}
920+
assert.Equal(t, exp, conflict.String())
921+
}
922+
})
923+
}
924+
}

0 commit comments

Comments
 (0)