@@ -2424,10 +2424,11 @@ func TestHandler_FinalizeOrder(t *testing.T) {
24242424 assert .FatalError (t , err )
24252425
24262426 type test struct {
2427- db acme.DB
2428- ctx context.Context
2429- statusCode int
2430- err * acme.Error
2427+ db acme.DB
2428+ ctx context.Context
2429+ statusCode int
2430+ err * acme.Error
2431+ expectedStatus acme.Status // for success cases; empty means skip status assertion
24312432 }
24322433 var tests = map [string ]func (t * testing.T ) test {
24332434 "fail/no-account" : func (t * testing.T ) test {
@@ -2569,7 +2570,9 @@ func TestHandler_FinalizeOrder(t *testing.T) {
25692570 err : acme .NewError (acme .ErrorUnauthorizedType , "provisioner id mismatch" ),
25702571 }
25712572 },
2572- "fail/order-finalize-error" : func (t * testing.T ) test {
2573+ // Tests that an expired ready order fails at UpdateStatus, which tries to
2574+ // write StatusInvalid to the DB and gets an error back.
2575+ "fail/update-status-error" : func (t * testing.T ) test {
25732576 acc := & acme.Account {ID : "accountID" }
25742577 ctx := acme .NewProvisionerContext (context .Background (), prov )
25752578 ctx = context .WithValue (ctx , accContextKey , acc )
@@ -2594,7 +2597,8 @@ func TestHandler_FinalizeOrder(t *testing.T) {
25942597 err : acme .NewErrorISE ("force" ),
25952598 }
25962599 },
2597- "ok" : func (t * testing.T ) test {
2600+ // Tests that a DB failure when writing the processing status returns 500.
2601+ "fail/db.UpdateOrder-processing-error" : func (t * testing.T ) test {
25982602 acc := & acme.Account {ID : "accountID" }
25992603 ctx := acme .NewProvisionerContext (context .Background (), prov )
26002604 ctx = context .WithValue (ctx , accContextKey , acc )
@@ -2604,30 +2608,66 @@ func TestHandler_FinalizeOrder(t *testing.T) {
26042608 db : & acme.MockDB {
26052609 MockGetOrder : func (ctx context.Context , id string ) (* acme.Order , error ) {
26062610 return & acme.Order {
2607- ID : "orderID" ,
2608- AccountID : "accountID" ,
2609- ProvisionerID : fmt .Sprintf ("acme/%s" , prov .GetName ()),
2610- ExpiresAt : naf ,
2611- Status : acme .StatusValid ,
2612- AuthorizationIDs : []string {"foo" , "bar" , "baz" },
2613- NotBefore : nbf ,
2614- NotAfter : naf ,
2615- Identifiers : []acme.Identifier {
2616- {
2617- Type : "dns" ,
2618- Value : "example.com" ,
2619- },
2620- {
2621- Type : "dns" ,
2622- Value : "*.smallstep.com" ,
2623- },
2624- },
2625- CertificateID : "certID" ,
2611+ ID : "orderID" ,
2612+ AccountID : "accountID" ,
2613+ ProvisionerID : fmt .Sprintf ("acme/%s" , prov .GetName ()),
2614+ ExpiresAt : naf ,
2615+ Status : acme .StatusReady ,
26262616 }, nil
26272617 },
2618+ MockUpdateOrder : func (ctx context.Context , o * acme.Order ) error {
2619+ return acme .NewErrorISE ("force" )
2620+ },
26282621 },
26292622 ctx : ctx ,
2630- statusCode : 200 ,
2623+ statusCode : 500 ,
2624+ err : acme .NewErrorISE ("force" ),
2625+ }
2626+ },
2627+ // Tests the happy path: a ready order immediately returns processing + Retry-After,
2628+ // and the background goroutine is short-circuited by an error on re-fetch.
2629+ "ok" : func (t * testing.T ) test {
2630+ acc := & acme.Account {ID : "accountID" }
2631+ ctx := acme .NewProvisionerContext (context .Background (), prov )
2632+ ctx = context .WithValue (ctx , accContextKey , acc )
2633+ ctx = context .WithValue (ctx , payloadContextKey , & payloadInfo {value : payloadBytes })
2634+ ctx = context .WithValue (ctx , chi .RouteCtxKey , chiCtx )
2635+ // firstCall acts as a one-shot token: the HTTP handler consumes it,
2636+ // the background goroutine gets the default error path and exits early.
2637+ firstCall := make (chan struct {}, 1 )
2638+ firstCall <- struct {}{}
2639+ return test {
2640+ db : & acme.MockDB {
2641+ MockGetOrder : func (ctx context.Context , id string ) (* acme.Order , error ) {
2642+ select {
2643+ case <- firstCall :
2644+ return & acme.Order {
2645+ ID : "orderID" ,
2646+ AccountID : "accountID" ,
2647+ ProvisionerID : fmt .Sprintf ("acme/%s" , prov .GetName ()),
2648+ ExpiresAt : naf ,
2649+ Status : acme .StatusReady ,
2650+ AuthorizationIDs : []string {"foo" , "bar" , "baz" },
2651+ NotBefore : nbf ,
2652+ NotAfter : naf ,
2653+ Identifiers : []acme.Identifier {
2654+ {Type : "dns" , Value : "example.com" },
2655+ {Type : "dns" , Value : "*.smallstep.com" },
2656+ },
2657+ }, nil
2658+ default :
2659+ // Second call is from the background goroutine; return an
2660+ // error to short-circuit it so it doesn't race with assertions.
2661+ return nil , acme .NewErrorISE ("test: short-circuit goroutine" )
2662+ }
2663+ },
2664+ MockUpdateOrder : func (ctx context.Context , o * acme.Order ) error {
2665+ return nil
2666+ },
2667+ },
2668+ ctx : ctx ,
2669+ statusCode : 200 ,
2670+ expectedStatus : acme .StatusProcessing ,
26312671 }
26322672 },
26332673 }
@@ -2656,13 +2696,15 @@ func TestHandler_FinalizeOrder(t *testing.T) {
26562696 assert .Equals (t , ae .Subproblems , tc .err .Subproblems )
26572697 assert .Equals (t , res .Header ["Content-Type" ], []string {"application/problem+json" })
26582698 } else {
2659- expB , err := json .Marshal (o )
2660- assert .FatalError (t , err )
2661-
26622699 ro := new (acme.Order )
2663- assert .FatalError (t , json .Unmarshal (body , ro ))
2700+ assert .FatalError (t , json .Unmarshal (bytes . TrimSpace ( body ) , ro ))
26642701
2665- assert .Equals (t , bytes .TrimSpace (body ), expB )
2702+ if tc .expectedStatus != "" {
2703+ assert .Equals (t , ro .Status , tc .expectedStatus )
2704+ }
2705+ if tc .expectedStatus == acme .StatusProcessing {
2706+ assert .Equals (t , res .Header ["Retry-After" ], []string {"15" })
2707+ }
26662708 assert .Equals (t , res .Header ["Location" ], []string {u })
26672709 assert .Equals (t , res .Header ["Content-Type" ], []string {"application/json" })
26682710 }
0 commit comments