Skip to content

Commit 37a23aa

Browse files
committed
add tests + basic LRO service
1 parent a469f83 commit 37a23aa

7 files changed

Lines changed: 222 additions & 12 deletions

File tree

buf.gen.yaml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
version: v1
1+
version: v2
2+
managed:
3+
enabled: true
4+
override:
5+
# temporary workaround until https://github.com/aep-dev/aep-components/pull/22
6+
# is merged and released
7+
- module: buf.build/aep/api
8+
file_option: go_package_prefix
9+
value: buf.build/gen/go/aep/api/protocolbuffers/go
210
plugins:
3-
- plugin: buf.build/protocolbuffers/go:v1.31.0
11+
- remote: buf.build/protocolbuffers/go:v1.31.0
412
out: .
513
opt: paths=source_relative
6-
- plugin: buf.build/grpc-ecosystem/gateway:v2.18.0
14+
- remote: buf.build/grpc-ecosystem/gateway:v2.18.0
715
out: .
816
opt: paths=source_relative
9-
- plugin: buf.build/grpc-ecosystem/openapiv2:v2.18.0
17+
- remote: buf.build/grpc-ecosystem/openapiv2:v2.18.0
1018
out: .
11-
- plugin: buf.build/grpc/go:v1.3.0
19+
- remote: buf.build/grpc/go:v1.3.0
1220
out: .
1321
opt:
1422
- paths=source_relative

example/bookstore/v1/bookstore.pb.go

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

example/bookstore/v1/bookstore.proto

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ service Bookstore {
6969
};
7070
}
7171

72-
// archive a book.
72+
// archive abook.
7373
rpc archiveBook ( ArchiveBookRequest ) returns ( aep.api.Operation ) {
7474
option (google.api.http) = {
7575
post: "/{path=publishers/*/books/*}:archive",

example/bookstore/v1/bookstore_grpc.pb.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/service/service.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,64 @@ import (
77
"log"
88
"log/slog"
99
"net"
10+
"sync"
11+
"time"
1012

1113
"google.golang.org/grpc"
1214
"google.golang.org/grpc/codes"
1315
"google.golang.org/grpc/status"
1416

17+
_ "buf.build/gen/go/aep/api/protocolbuffers/go/aep/api"
18+
api "buf.build/gen/go/aep/api/protocolbuffers/go/aep/api"
19+
lrpb "cloud.google.com/go/longrunning/autogen/longrunningpb"
1520
bpb "github.com/aep-dev/aepc/example/bookstore/v1"
1621
_ "github.com/mattn/go-sqlite3" // sqlite3 driver
1722
"google.golang.org/protobuf/proto"
23+
"google.golang.org/protobuf/types/known/anypb"
1824
"google.golang.org/protobuf/types/known/emptypb"
1925
)
2026

27+
type operationStatus struct {
28+
Done bool
29+
Result interface{}
30+
Error error
31+
}
32+
33+
type operationStore struct {
34+
mu sync.Mutex
35+
operations map[string]*operationStatus
36+
}
37+
38+
var opStore = &operationStore{
39+
operations: make(map[string]*operationStatus),
40+
}
41+
42+
func (s *operationStore) createOperation(id string) {
43+
s.mu.Lock()
44+
defer s.mu.Unlock()
45+
s.operations[id] = &operationStatus{Done: false}
46+
}
47+
48+
func (s *operationStore) completeOperation(id string, result interface{}, err error) {
49+
s.mu.Lock()
50+
defer s.mu.Unlock()
51+
if op, exists := s.operations[id]; exists {
52+
op.Done = true
53+
op.Result = result
54+
op.Error = err
55+
}
56+
}
57+
58+
func (s *operationStore) getOperation(id string) (*operationStatus, bool) {
59+
s.mu.Lock()
60+
defer s.mu.Unlock()
61+
op, exists := s.operations[id]
62+
return op, exists
63+
}
64+
2165
type BookstoreServer struct {
2266
bpb.UnimplementedBookstoreServer
67+
lrpb.UnimplementedOperationsServer
2368
db *sql.DB
2469
}
2570

@@ -166,6 +211,64 @@ func (s BookstoreServer) ListBooks(_ context.Context, r *bpb.ListBooksRequest) (
166211
return &bpb.ListBooksResponse{Results: books}, nil
167212
}
168213

214+
func (s BookstoreServer) ArchiveBook(ctx context.Context, r *bpb.ArchiveBookRequest) (*api.Operation, error) {
215+
log.Printf("archiving book %q", r.Path)
216+
217+
operationID := fmt.Sprintf("op-%d", time.Now().UnixNano())
218+
opStore.createOperation(operationID)
219+
220+
go func() {
221+
// Simulate the archiving process
222+
result, err := s.db.Exec(`
223+
UPDATE books
224+
SET published = false
225+
WHERE path = ?`,
226+
r.Path)
227+
if err != nil {
228+
opStore.completeOperation(operationID, nil, status.Errorf(codes.Internal, "failed to archive book: %v", err))
229+
return
230+
}
231+
232+
rows, err := result.RowsAffected()
233+
if err != nil {
234+
opStore.completeOperation(operationID, nil, status.Errorf(codes.Internal, "failed to get rows affected: %v", err))
235+
return
236+
}
237+
if rows == 0 {
238+
opStore.completeOperation(operationID, nil, status.Errorf(codes.NotFound, "book %q not found", r.Path))
239+
return
240+
}
241+
242+
opStore.completeOperation(operationID, &emptypb.Empty{}, nil)
243+
}()
244+
245+
return &api.Operation{Path: operationID, Done: false}, nil
246+
}
247+
248+
func (s BookstoreServer) GetOperation(ctx context.Context, r *lrpb.GetOperationRequest) (*lrpb.Operation, error) {
249+
op, exists := opStore.getOperation(r.Name)
250+
if !exists {
251+
return nil, status.Errorf(codes.NotFound, "operation %q not found", r.Name)
252+
}
253+
254+
operation := &lrpb.Operation{
255+
Name: r.Name,
256+
Done: op.Done,
257+
}
258+
259+
if op.Error != nil {
260+
operation.Result = &lrpb.Operation_Error{
261+
Error: status.Convert(op.Error).Proto(),
262+
}
263+
} else {
264+
operation.Result = &lrpb.Operation_Response{
265+
Response: op.Result.(*anypb.Any),
266+
}
267+
}
268+
269+
return operation, nil
270+
}
271+
169272
func (s BookstoreServer) CreatePublisher(_ context.Context, r *bpb.CreatePublisherRequest) (*bpb.Publisher, error) {
170273
publisher := proto.Clone(r.Publisher).(*bpb.Publisher)
171274
log.Printf("creating publisher %q", r)

example/service/service_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package service
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"testing"
7+
"time"
8+
9+
lrpb "cloud.google.com/go/longrunning/autogen/longrunningpb"
10+
bpb "github.com/aep-dev/aepc/example/bookstore/v1"
11+
_ "github.com/mattn/go-sqlite3"
12+
)
13+
14+
func setupTestDB(t *testing.T) *sql.DB {
15+
db, err := sql.Open("sqlite3", ":memory:")
16+
if err != nil {
17+
t.Fatalf("failed to open test database: %v", err)
18+
}
19+
20+
_, err = db.Exec(`
21+
CREATE TABLE books (
22+
path TEXT PRIMARY KEY,
23+
author TEXT,
24+
price REAL,
25+
published BOOLEAN,
26+
edition INTEGER,
27+
isbn TEXT
28+
);
29+
`)
30+
if err != nil {
31+
t.Fatalf("failed to create test table: %v", err)
32+
}
33+
34+
return db
35+
}
36+
37+
func TestArchiveBookOperation(t *testing.T) {
38+
db := setupTestDB(t)
39+
defer db.Close()
40+
41+
s := NewBookstoreServer(db)
42+
43+
// Insert a test book
44+
_, err := db.Exec(`
45+
INSERT INTO books (path, author, price, published, edition, isbn)
46+
VALUES (?, ?, ?, ?, ?, ?)
47+
`, "publishers/1/books/1", "Author Name", 19.99, true, 1, "1234567890")
48+
if err != nil {
49+
t.Fatalf("failed to insert test book: %v", err)
50+
}
51+
52+
// Archive the book
53+
r := &bpb.ArchiveBookRequest{Path: "publishers/1/books/1"}
54+
operation, err := s.ArchiveBook(context.Background(), r)
55+
if err != nil {
56+
t.Fatalf("ArchiveBook failed: %v", err)
57+
}
58+
59+
if operation.Done {
60+
t.Fatalf("expected operation to not be done immediately, got true")
61+
}
62+
63+
// Simulate waiting for the operation to complete
64+
time.Sleep(100 * time.Millisecond)
65+
66+
// Check operation status
67+
getOpReq := &lrpb.GetOperationRequest{Name: operation.Path}
68+
opResp, err := s.GetOperation(context.Background(), getOpReq)
69+
if err != nil {
70+
t.Fatalf("GetOperation failed: %v", err)
71+
}
72+
73+
if !opResp.Done {
74+
t.Fatalf("expected operation to be done, got false")
75+
}
76+
77+
// Verify the book is archived
78+
var published bool
79+
err = db.QueryRow("SELECT published FROM books WHERE path = ?", "publishers/1/books/1").Scan(&published)
80+
if err != nil {
81+
t.Fatalf("failed to query book: %v", err)
82+
}
83+
84+
if published {
85+
t.Fatalf("expected published to be false, got true")
86+
}
87+
}

schema/resourcedefinition.pb.go

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

0 commit comments

Comments
 (0)