Skip to content

Commit 205b9ab

Browse files
committed
feat: Add support for manual orders (Resolves #580)
1 parent 9f02d08 commit 205b9ab

File tree

7 files changed

+511
-1
lines changed

7 files changed

+511
-1
lines changed

external/completion/completion.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ func Complete(c Request) ([]string, cobra.ShellCompDirective) {
187187

188188
if regexOptions, err := rt.GetCompletionOptions(); err == nil {
189189
autoCompleteAttributes = append(autoCompleteAttributes, regexOptions...)
190+
} else {
191+
log.Debugf("Could not complete options %v", err)
190192
}
191193

192194
for _, k := range autoCompleteAttributes {

external/completion/completion_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,35 @@ func TestCompleteAttributeKeyWithWhenAndSatisfiedExistingValuesReturnsSatisfiedC
821821
require.Len(t, completions, 9)
822822
}
823823

824+
// This test might be redundant but regex was broken for this resource at a time
825+
func TestCompleteAttributeKeyWithEmptyExistingValuesReturnsAllIncludingRegex(t *testing.T) {
826+
// Fixture Setup
827+
toComplete := ""
828+
manualOrder := resources.MustGetResourceByName("manual-order")
829+
request := Request{
830+
Type: CompleteAttributeKey,
831+
Verb: Create,
832+
ToComplete: toComplete,
833+
Resource: manualOrder,
834+
Attributes: map[string]string{
835+
"meta.display_price.with_tax.currency": "USD",
836+
"meta.display_price.balance_owing.formatted": "$10.00",
837+
},
838+
}
839+
840+
// Exercise SUT
841+
completions, compDir := Complete(request)
842+
843+
// Verify Results
844+
require.Equal(t, compDir, cobra.ShellCompDirectiveNoFileComp)
845+
846+
require.Contains(t, completions, "meta.display_price.with_tax.amount")
847+
require.Contains(t, completions, "meta.display_price.balance_owing.currency")
848+
require.NotContains(t, completions, "meta.display_price.with_tax.currency")
849+
require.NotContains(t, completions, "meta.display_price.balance_owing.formatted")
850+
require.Contains(t, completions, "meta.display_price.paid")
851+
}
852+
824853
func TestCompleteAttributeKeyWithWhenSkippingWhen(t *testing.T) {
825854
// Fixture Setup
826855
toComplete := ""

external/resources/yaml/carts-and-orders.yaml

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,41 @@ cart-items:
7171
type: INT
7272
id:
7373
type: RESOURCE_ID:pcm-products
74+
cart-custom-discounts:
75+
singular-name: "cart-custom-discount"
76+
json-api-type: "custom_discount"
77+
json-api-format: "legacy"
78+
no-wrapping: true
79+
docs: "https://developer.elasticpath.com/docs/api/carts/bulk-add-custom-discounts-to-cart"
80+
create-entity:
81+
docs: "https://developer.elasticpath.com/docs/api/carts/bulk-add-custom-discounts-to-cart"
82+
url: "/v2/carts/{carts}/custom-discounts"
83+
openapi-operation-id: bulkAddCustomDiscountsToCart
84+
content-type: application/json
85+
delete-entity:
86+
docs: "https://developer.elasticpath.com/docs/api/carts/bulk-delete-custom-discounts-from-cart"
87+
url: "/v2/carts/{carts}/custom-discounts"
88+
openapi-operation-id: bulkDeleteCustomDiscountsFromCart
89+
content-type: application/json
90+
attributes:
91+
data[n].amount:
92+
type: INT
93+
data[n].amount.amount:
94+
type: INT
95+
data[n].amount.currency:
96+
type: CURRENCY
97+
data[n].amount.formatted:
98+
type: STRING
99+
data[n].external_id:
100+
type: STRING
101+
data[n].discount_code:
102+
type: STRING
103+
data[n].type:
104+
type: CONST:custom_discount
105+
data[n].description:
106+
type: STRING
107+
108+
74109
cart-product-items:
75110
singular-name: "cart-product-item"
76111
json-api-type: "cart_item"
@@ -387,3 +422,153 @@ order-transaction-refund:
387422
docs: "https://elasticpath.dev/docs/carts-orders/refund-a-transaction"
388423
url: "/v2/orders/{orders}/transactions/{order_transactions}/refund"
389424
openapi-operation-id: refundATransaction
425+
426+
manual-orders:
427+
singular-name: "manual-order"
428+
json-api-type: order
429+
json-api-format: legacy
430+
docs: "https://elasticpath.dev/docs/api/carts/orders"
431+
create-entity:
432+
docs: "https://elasticpath.dev/docs/api/carts/orders"
433+
url: "/v2/orders"
434+
attributes:
435+
id:
436+
type: STRING
437+
usage: "Optional custom order ID. If not provided, a UUID will be generated."
438+
status:
439+
type: ENUM:incomplete,processing,cancelled,complete
440+
usage: "The status of the order."
441+
payment:
442+
type: ENUM:paid,unpaid,refunded,partially_authorized,partially_paid
443+
usage: "The payment status of the order."
444+
shipping:
445+
type: ENUM:fulfilled,unfulfilled
446+
usage: "The shipping status of the order."
447+
anonymized:
448+
type: BOOL
449+
usage: "Whether the order should be anonymized."
450+
account.id:
451+
type: RESOURCE_ID:account
452+
usage: "The unique identifier of the account."
453+
account.member_id:
454+
type: RESOURCE_ID:account-member
455+
usage: "The unique identifier of the account member."
456+
contact.name:
457+
type: STRING
458+
usage: "Contact name for the order."
459+
contact.email:
460+
type: STRING
461+
usage: "Contact email for the order."
462+
customer.id:
463+
type: RESOURCE_ID:customer
464+
usage: "The unique identifier of the customer."
465+
autofill: FUNC:UUID
466+
customer.email:
467+
type: STRING
468+
usage: "Customer email address."
469+
autofill: FUNC:Email
470+
customer.name:
471+
type: STRING
472+
usage: "Customer name."
473+
autofill: FUNC:Name
474+
shipping_address.first_name:
475+
type: STRING
476+
autofill: FUNC:FirstName
477+
shipping_address.last_name:
478+
type: STRING
479+
autofill: FUNC:LastName
480+
shipping_address.company_name:
481+
type: STRING
482+
autofill: FUNC:Company
483+
shipping_address.line_1:
484+
type: STRING
485+
autofill: FUNC:Street
486+
shipping_address.line_2:
487+
type: STRING
488+
shipping_address.city:
489+
type: STRING
490+
autofill: FUNC:City
491+
shipping_address.county:
492+
type: STRING
493+
shipping_address.region:
494+
type: STRING
495+
autofill: FUNC:State
496+
shipping_address.postcode:
497+
type: STRING
498+
autofill: FUNC:Zip
499+
shipping_address.country:
500+
type: STRING
501+
autofill: FUNC:Country
502+
billing_address.first_name:
503+
type: STRING
504+
autofill: FUNC:FirstName
505+
billing_address.last_name:
506+
type: STRING
507+
autofill: FUNC:LastName
508+
billing_address.company_name:
509+
type: STRING
510+
autofill: FUNC:Company
511+
billing_address.line_1:
512+
type: STRING
513+
autofill: FUNC:Street
514+
billing_address.line_2:
515+
type: STRING
516+
billing_address.city:
517+
type: STRING
518+
autofill: FUNC:City
519+
billing_address.county:
520+
type: STRING
521+
billing_address.region:
522+
type: STRING
523+
autofill: FUNC:State
524+
billing_address.postcode:
525+
type: STRING
526+
autofill: FUNC:Zip
527+
billing_address.country:
528+
type: STRING
529+
autofill: FUNC:Country
530+
^meta\.display_price\.(with_tax|without_tax|tax|discount|balance_owing|paid|authorized|without_discount|shipping|shipping_discount)\.amount$:
531+
type: INT
532+
usage: "The amount of the order specified in currency subunits"
533+
^meta\.display_price\.(with_tax|without_tax|tax|discount|balance_owing|paid|authorized|without_discount|shipping|shipping_discount)\.currency$:
534+
type: CURRENCY
535+
usage: "The currency"
536+
^meta\.display_price\.(with_tax|without_tax|tax|discount|balance_owing|paid|authorized|without_discount|shipping|shipping_discount)\.formatted$:
537+
type: STRING
538+
usage: "The amount of the order specified as a formatted string"
539+
included.items[n].id:
540+
type: STRING
541+
usage: "The unique identifier of the item."
542+
included.items[n].type:
543+
type: CONST:order_item
544+
included.items[n].quantity:
545+
type: INT
546+
included.items[n].location:
547+
type: STRING
548+
included.items[n].product_id:
549+
type: RESOURCE_ID:pcm-product
550+
included.items[n].subscription_offering_id:
551+
type: RESOURCE_ID:subscription-offerings
552+
included.items[n].name:
553+
type: STRING
554+
included.items[n].sku:
555+
type: STRING
556+
^included\.items\[n\]\.(unit_price|value)\.amount$:
557+
type: INT
558+
usage: "An amount in currency subunits if applicable (e.g., $100.00 would be 10000)"
559+
^included\.items\[n\]\.(unit_price|value)\.currency$:
560+
type: CURRENCY
561+
^included\.items\[n\]\.(unit_price|value)\.include_tax$:
562+
type: BOOL
563+
^included\.items\[n\]\.meta\.display_price\.(with_tax|without_tax|tax|discount_without_discount)\.(unit|value)\.amount$:
564+
type: INT
565+
usage: "An amount in currency subunits if applicable (e.g., $100.00 would be 10000)"
566+
^included\.items\[n\]\.meta\.display_price\.(with_tax|without_tax|tax|discount_without_discount)\.(unit|value)\.currency$:
567+
type: CURRENCY
568+
^included\.items\[n\]\.meta\.display_price\.(with_tax|without_tax|tax|discount_without_discount)\.(unit|value)\.formatted$:
569+
usage: "A formatted amount e.g., $100.00"
570+
type: STRING
571+
included.items[n].meta.timestamps.created_at:
572+
type: STRING
573+
included.items[n].meta.timestamps.updated_at:
574+
type: STRING

external/resources/yaml/resources_yaml_test.go

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/expr-lang/expr"
1919
"github.com/expr-lang/expr/ast"
2020
"github.com/expr-lang/expr/parser"
21+
"github.com/quasilyte/regex/syntax"
2122
"github.com/santhosh-tekuri/jsonschema/v4"
2223
log "github.com/sirupsen/logrus"
2324
"github.com/stretchr/testify/assert"
@@ -34,7 +35,7 @@ func TestExpectedNumberOfResources(t *testing.T) {
3435
resourceCount := len(resources.GetPluralResources())
3536

3637
// Verification
37-
require.Equal(t, resourceCount, 138)
38+
require.Equal(t, resourceCount, 140)
3839
}
3940

4041
func TestCreatedByTemplatesAllReferenceValidResource(t *testing.T) {
@@ -244,6 +245,15 @@ func validateAttributeInfo(info *resources.CrudEntityAttribute) string {
244245
errors += fmt.Sprintf("\t attribute `%s` is a regex, but the completion tree doesn't support it: %v", info.Key, err)
245246
}
246247

248+
p := syntax.NewParser(&syntax.ParserOptions{})
249+
parse, err := p.Parse(info.Key)
250+
251+
err = validateRegexTreeContainsNoSingleCharClass(parse)
252+
253+
if err != nil {
254+
errors += fmt.Sprintf("\t attribute `%s` is a regex, but it contains a single char class: %v", info.Key, err)
255+
}
256+
247257
}
248258
}
249259
if match {
@@ -264,6 +274,34 @@ func validateAttributeInfo(info *resources.CrudEntityAttribute) string {
264274

265275
return errors
266276
}
277+
278+
func validateRegexTreeContainsNoSingleCharClass(tree *syntax.Regexp) error {
279+
280+
var checkChildren func(e *syntax.Expr) error
281+
checkChildren = func(e *syntax.Expr) error {
282+
283+
if e.Op == syntax.OpCharClass {
284+
if len(e.Args) == 1 {
285+
return fmt.Errorf("single char class found: %v", e.Value)
286+
}
287+
}
288+
289+
for _, child := range e.Args {
290+
291+
err := checkChildren(&child)
292+
293+
if err != nil {
294+
return err
295+
}
296+
297+
}
298+
299+
return nil
300+
}
301+
302+
return checkChildren(&tree.Expr)
303+
}
304+
267305
func validateCrudEntityInfo(info resources.CrudEntityInfo) string {
268306
errors := ""
269307

0 commit comments

Comments
 (0)