ecomcoder-cli 1.2.12 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +293 -0
  2. package/dist/__tests__/test-utils.d.ts +123 -0
  3. package/dist/__tests__/test-utils.d.ts.map +1 -0
  4. package/dist/__tests__/test-utils.js +133 -0
  5. package/dist/__tests__/test-utils.js.map +1 -0
  6. package/dist/cli.d.ts +8 -7
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +70 -65
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/docs/index.d.ts +10 -0
  11. package/dist/commands/docs/index.d.ts.map +1 -0
  12. package/dist/commands/docs/index.js +43 -0
  13. package/dist/commands/docs/index.js.map +1 -0
  14. package/dist/commands/metafield/index.d.ts +10 -0
  15. package/dist/commands/metafield/index.d.ts.map +1 -0
  16. package/dist/commands/metafield/index.js +55 -0
  17. package/dist/commands/metafield/index.js.map +1 -0
  18. package/dist/commands/product/__tests__/service.test.d.ts +7 -0
  19. package/dist/commands/product/__tests__/service.test.d.ts.map +1 -0
  20. package/dist/commands/product/__tests__/service.test.js +299 -0
  21. package/dist/commands/product/__tests__/service.test.js.map +1 -0
  22. package/dist/commands/product/__tests__/update-description.test.d.ts +5 -0
  23. package/dist/commands/product/__tests__/update-description.test.d.ts.map +1 -0
  24. package/dist/commands/product/__tests__/update-description.test.js +110 -0
  25. package/dist/commands/product/__tests__/update-description.test.js.map +1 -0
  26. package/dist/commands/product/get.d.ts +2 -0
  27. package/dist/commands/product/get.d.ts.map +1 -0
  28. package/dist/commands/product/get.js +305 -0
  29. package/dist/commands/product/get.js.map +1 -0
  30. package/dist/commands/product/index.d.ts +10 -0
  31. package/dist/commands/product/index.d.ts.map +1 -0
  32. package/dist/commands/product/index.js +73 -0
  33. package/dist/commands/product/index.js.map +1 -0
  34. package/dist/commands/product/queries.d.ts +32 -0
  35. package/dist/commands/product/queries.d.ts.map +1 -0
  36. package/dist/commands/product/queries.js +195 -0
  37. package/dist/commands/product/queries.js.map +1 -0
  38. package/dist/commands/product/service.d.ts +51 -0
  39. package/dist/commands/product/service.d.ts.map +1 -0
  40. package/dist/commands/product/service.js +237 -0
  41. package/dist/commands/product/service.js.map +1 -0
  42. package/dist/commands/product/types.d.ts +165 -0
  43. package/dist/commands/product/types.d.ts.map +1 -0
  44. package/dist/commands/product/types.js +6 -0
  45. package/dist/commands/product/types.js.map +1 -0
  46. package/dist/commands/product/update-description.d.ts +8 -0
  47. package/dist/commands/product/update-description.d.ts.map +1 -0
  48. package/dist/commands/product/update-description.js +86 -0
  49. package/dist/commands/product/update-description.js.map +1 -0
  50. package/dist/commands/product/update-price.d.ts +8 -0
  51. package/dist/commands/product/update-price.d.ts.map +1 -0
  52. package/dist/commands/product/update-price.js +101 -0
  53. package/dist/commands/product/update-price.js.map +1 -0
  54. package/dist/commands/product/update-template.d.ts +8 -0
  55. package/dist/commands/product/update-template.d.ts.map +1 -0
  56. package/dist/commands/product/update-template.js +114 -0
  57. package/dist/commands/product/update-template.js.map +1 -0
  58. package/dist/commands/product/utils.d.ts +69 -0
  59. package/dist/commands/product/utils.d.ts.map +1 -0
  60. package/dist/commands/product/utils.js +180 -0
  61. package/dist/commands/product/utils.js.map +1 -0
  62. package/dist/commands/products/index.d.ts +10 -0
  63. package/dist/commands/products/index.d.ts.map +1 -0
  64. package/dist/commands/products/index.js +62 -0
  65. package/dist/commands/products/index.js.map +1 -0
  66. package/dist/lib/api-client.d.ts.map +1 -1
  67. package/dist/lib/api-client.js +11 -0
  68. package/dist/lib/api-client.js.map +1 -1
  69. package/dist/lib/args-parser.d.ts.map +1 -1
  70. package/dist/lib/args-parser.js +2 -1
  71. package/dist/lib/args-parser.js.map +1 -1
  72. package/dist/lib/command-registry.d.ts +64 -0
  73. package/dist/lib/command-registry.d.ts.map +1 -0
  74. package/dist/lib/command-registry.js +76 -0
  75. package/dist/lib/command-registry.js.map +1 -0
  76. package/package.json +11 -3
  77. package/dist/lib/gemini-client.d.ts +0 -6
  78. package/dist/lib/gemini-client.d.ts.map +0 -1
  79. package/dist/lib/gemini-client.js +0 -27
  80. package/dist/lib/gemini-client.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,16 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * EcomCoder CLI - Main Entry Point
3
+ * EcomCoder CLI - Main Entry Point (Refactored)
4
4
  *
5
- * Command structure:
6
- * ecomcoder products create --count=10
7
- * ecomcoder products list --limit=50
8
- * ecomcoder products set-rating --product-id=... --rating=4.5 --count=127
9
- * ecomcoder metafield create --name=... --key=... --type=...
10
- * ecomcoder metafield get --namespace=... --key=...
5
+ * Architecture: Command Registry Pattern
6
+ *
7
+ * Benefits:
8
+ * Open/Closed Principle - Add commands without modifying this file
9
+ * Single Responsibility - This file only routes, doesn't know command details
10
+ * Scalability - Supports unlimited commands
11
+ * ✅ Testability - Easy to test routing logic
11
12
  */
12
13
  import { getPositionalArgs } from './lib/args-parser.js';
13
- function showHelp() {
14
+ import { registry } from './lib/command-registry.js';
15
+ // Register all command groups
16
+ import { registerProductCommands } from './commands/product/index.js';
17
+ import { registerMetafieldCommands } from './commands/metafield/index.js';
18
+ import { registerProductsCommands } from './commands/products/index.js';
19
+ import { registerDocsCommands } from './commands/docs/index.js';
20
+ // Initialize registry
21
+ registerProductCommands();
22
+ registerMetafieldCommands();
23
+ registerProductsCommands();
24
+ registerDocsCommands();
25
+ function showMainHelp() {
14
26
  console.log(`
15
27
  EcomCoder CLI - Shopify Development Utilities
16
28
 
@@ -18,12 +30,16 @@ USAGE:
18
30
  ecomcoder <command> <subcommand> [options]
19
31
 
20
32
  COMMANDS:
21
- products create Create dummy Shopify products for testing
22
- products list List products in your Shopify store
23
- products set-rating Set rating metafields on a product
24
- metafield create Create metafield definition
25
- metafield get Get metafield definition(s)
26
- docs search Search Shopify theme documentation
33
+ products create Create dummy Shopify products for testing
34
+ products list List products in your Shopify store
35
+ products set-rating Set rating metafields on a product
36
+ product get Get detailed product data
37
+ product update-description Update product description
38
+ product update-price Update product price and compare-at price
39
+ product update-template Change product template assignment
40
+ metafield create Create metafield definition
41
+ metafield get Get metafield definition(s)
42
+ docs search Search Shopify theme documentation
27
43
 
28
44
  GLOBAL OPTIONS:
29
45
  --session-id Session ID for authentication (auto-provided when used with Claude)
@@ -46,6 +62,19 @@ EXAMPLES:
46
62
  # Set product rating
47
63
  ecomcoder products set-rating --product-id="gid://shopify/Product/123" --rating=4.5 --count=127
48
64
 
65
+ # Get product details
66
+ ecomcoder product get --id=123456789
67
+ ecomcoder product get --title="Blue T-Shirt"
68
+
69
+ # Update product description
70
+ ecomcoder product update-description --id=123 --description="Premium cotton t-shirt"
71
+
72
+ # Update product price
73
+ ecomcoder product update-price --id=123 --price=34.99 --compare-at-price=49.99
74
+
75
+ # Change product template
76
+ ecomcoder product update-template --id=123 --template=premium
77
+
49
78
  # Create product-level metafield
50
79
  ecomcoder metafield create --name="Rating" --key=rating --type=number_decimal --owner-type=PRODUCT
51
80
 
@@ -64,71 +93,47 @@ For more help on specific commands, run:
64
93
  }
65
94
  async function main() {
66
95
  const args = process.argv.slice(2);
67
- // Show help only if no arguments (not if help flag is after a subcommand)
96
+ // Show help if no arguments
68
97
  if (args.length === 0) {
69
- showHelp();
98
+ showMainHelp();
70
99
  process.exit(0);
71
100
  }
72
101
  const positional = getPositionalArgs(args);
73
102
  const [command, subcommand] = positional;
74
103
  // Show main help if help flag with no command
75
104
  if ((args.includes('--help') || args.includes('-h')) && !command) {
76
- showHelp();
105
+ showMainHelp();
77
106
  process.exit(0);
78
107
  }
79
- // Route to appropriate command handler
80
108
  try {
81
- if (command === 'products') {
82
- if (subcommand === 'create') {
83
- const { run } = await import('./commands/create-dummy-products.js');
84
- await run(args.slice(2)); // Pass remaining args after 'products create'
85
- }
86
- else if (subcommand === 'list') {
87
- const { run } = await import('./commands/list-products.js');
88
- await run(args.slice(2));
89
- }
90
- else if (subcommand === 'set-rating') {
91
- const { run } = await import('./commands/set-product-rating.js');
92
- await run(args.slice(2));
93
- }
94
- else {
95
- console.error(`Unknown products subcommand: ${subcommand}`);
96
- console.error(`Valid subcommands: create, list, set-rating`);
97
- process.exit(1);
98
- }
99
- }
100
- else if (command === 'metafield') {
101
- if (subcommand === 'create') {
102
- const { run } = await import('./commands/create-metafield.js');
103
- await run(args.slice(2));
104
- }
105
- else if (subcommand === 'get') {
106
- const { run } = await import('./commands/get-metafield.js');
107
- await run(args.slice(2));
108
- }
109
- else {
110
- console.error(`Unknown metafield subcommand: ${subcommand}`);
111
- console.error(`Valid subcommands: create, get`);
112
- process.exit(1);
113
- }
114
- }
115
- else if (command === 'docs') {
116
- if (subcommand === 'search') {
117
- const { run } = await import('./commands/docs-search.js');
118
- await run(args.slice(2));
119
- }
120
- else {
121
- console.error(`Unknown docs subcommand: ${subcommand}`);
122
- console.error(`Valid subcommands: search`);
123
- process.exit(1);
124
- }
125
- }
126
- else {
109
+ // Check if command group exists
110
+ const group = registry.getGroup(command);
111
+ if (!group) {
112
+ // Command not found
113
+ const availableCommands = registry.getAllCommands();
127
114
  console.error(`Unknown command: ${command}`);
128
- console.error(`Valid commands: products, metafield, docs`);
115
+ console.error(`Valid commands: ${availableCommands.join(', ')}`);
129
116
  console.error(`Run 'ecomcoder --help' for more information`);
130
117
  process.exit(1);
131
118
  }
119
+ // Show group help ONLY if no subcommand
120
+ if (!subcommand) {
121
+ group.showHelp();
122
+ process.exit(0);
123
+ }
124
+ // Try to get command handler
125
+ const handler = await registry.get(command, subcommand);
126
+ if (!handler) {
127
+ // Subcommand not found
128
+ const availableCommands = registry.getCommandsInGroup(command)
129
+ .map(cmd => cmd.subcommand)
130
+ .filter(Boolean);
131
+ console.error(`Unknown ${command} subcommand: ${subcommand}`);
132
+ console.error(`Valid subcommands: ${availableCommands.join(', ')}`);
133
+ process.exit(1);
134
+ }
135
+ // Execute command (command itself handles --help)
136
+ await handler.run(args.slice(2)); // Pass remaining args
132
137
  }
133
138
  catch (error) {
134
139
  console.error(`Error executing command: ${error instanceof Error ? error.message : 'Unknown error'}`);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Docs Commands Registration
3
+ *
4
+ * Following Open/Closed Principle - commands register themselves
5
+ */
6
+ /**
7
+ * Register all docs commands
8
+ */
9
+ export declare function registerDocsCommands(): void;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/docs/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,IAAI,CAiC3C"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Docs Commands Registration
3
+ *
4
+ * Following Open/Closed Principle - commands register themselves
5
+ */
6
+ import { registry } from '../../lib/command-registry.js';
7
+ /**
8
+ * Register all docs commands
9
+ */
10
+ export function registerDocsCommands() {
11
+ // Register command group
12
+ registry.registerGroup({
13
+ command: 'docs',
14
+ description: 'Search Shopify theme documentation',
15
+ showHelp: () => {
16
+ console.log(`
17
+ Docs Commands - Search Shopify theme documentation
18
+
19
+ USAGE:
20
+ ecomcoder docs <subcommand> [options]
21
+
22
+ SUBCOMMANDS:
23
+ search Search Shopify theme docs using RAG
24
+
25
+ EXAMPLES:
26
+ # Search Shopify theme docs
27
+ ecomcoder docs search --query="How do sections work?"
28
+ ecomcoder docs search --query="Liquid filters for dates"
29
+
30
+ For detailed help on a specific subcommand:
31
+ ecomcoder docs <subcommand> --help
32
+ `);
33
+ }
34
+ });
35
+ // Register individual subcommands
36
+ registry.register({
37
+ command: 'docs',
38
+ subcommand: 'search',
39
+ description: 'Search Shopify theme documentation',
40
+ handler: async () => await import('../docs-search.js')
41
+ });
42
+ }
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/docs/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEzD;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,yBAAyB;IACzB,QAAQ,CAAC,aAAa,CAAC;QACrB,OAAO,EAAE,MAAM;QACf,WAAW,EAAE,oCAAoC;QACjD,QAAQ,EAAE,GAAG,EAAE;YACb,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;CAgBjB,CAAC,CAAC;QACC,CAAC;KACF,CAAC,CAAC;IAEH,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC;QAChB,OAAO,EAAE,MAAM;QACf,UAAU,EAAE,QAAQ;QACpB,WAAW,EAAE,oCAAoC;QACjD,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,MAAM,CAAC,mBAAmB,CAAC;KACvD,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Metafield Commands Registration
3
+ *
4
+ * Following Open/Closed Principle - commands register themselves
5
+ */
6
+ /**
7
+ * Register all metafield commands
8
+ */
9
+ export declare function registerMetafieldCommands(): void;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/metafield/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CA8ChD"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Metafield Commands Registration
3
+ *
4
+ * Following Open/Closed Principle - commands register themselves
5
+ */
6
+ import { registry } from '../../lib/command-registry.js';
7
+ /**
8
+ * Register all metafield commands
9
+ */
10
+ export function registerMetafieldCommands() {
11
+ // Register command group
12
+ registry.registerGroup({
13
+ command: 'metafield',
14
+ description: 'Manage Shopify metafield definitions',
15
+ showHelp: () => {
16
+ console.log(`
17
+ Metafield Commands - Manage Shopify metafield definitions
18
+
19
+ USAGE:
20
+ ecomcoder metafield <subcommand> [options]
21
+
22
+ SUBCOMMANDS:
23
+ create Create metafield definition
24
+ get Get metafield definition(s)
25
+
26
+ EXAMPLES:
27
+ # Create product-level metafield
28
+ ecomcoder metafield create --name="Rating" --key=rating --type=number_decimal --owner-type=PRODUCT
29
+
30
+ # Create shop-level metafield (global)
31
+ ecomcoder metafield create --name="Cart Upsell" --key=cart_upsell --type=product_reference --owner-type=SHOP
32
+
33
+ # Get metafield definition
34
+ ecomcoder metafield get --namespace=ecomcoder --key=upsell --owner-type=PRODUCT
35
+
36
+ For detailed help on a specific subcommand:
37
+ ecomcoder metafield <subcommand> --help
38
+ `);
39
+ }
40
+ });
41
+ // Register individual subcommands
42
+ registry.register({
43
+ command: 'metafield',
44
+ subcommand: 'create',
45
+ description: 'Create metafield definition',
46
+ handler: async () => await import('../create-metafield.js')
47
+ });
48
+ registry.register({
49
+ command: 'metafield',
50
+ subcommand: 'get',
51
+ description: 'Get metafield definition(s)',
52
+ handler: async () => await import('../get-metafield.js')
53
+ });
54
+ }
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/metafield/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEzD;;GAEG;AACH,MAAM,UAAU,yBAAyB;IACvC,yBAAyB;IACzB,QAAQ,CAAC,aAAa,CAAC;QACrB,OAAO,EAAE,WAAW;QACpB,WAAW,EAAE,sCAAsC;QACnD,QAAQ,EAAE,GAAG,EAAE;YACb,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;;;;;;CAsBjB,CAAC,CAAC;QACC,CAAC;KACF,CAAC,CAAC;IAEH,kCAAkC;IAClC,QAAQ,CAAC,QAAQ,CAAC;QAChB,OAAO,EAAE,WAAW;QACpB,UAAU,EAAE,QAAQ;QACpB,WAAW,EAAE,6BAA6B;QAC1C,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,MAAM,CAAC,wBAAwB,CAAC;KAC5D,CAAC,CAAC;IAEH,QAAQ,CAAC,QAAQ,CAAC;QAChB,OAAO,EAAE,WAAW;QACpB,UAAU,EAAE,KAAK;QACjB,WAAW,EAAE,6BAA6B;QAC1C,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,MAAM,CAAC,qBAAqB,CAAC;KACzD,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ProductService Tests
3
+ *
4
+ * Tests for product service layer business logic
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=service.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.test.d.ts","sourceRoot":"","sources":["../../../../src/commands/product/__tests__/service.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,299 @@
1
+ /**
2
+ * ProductService Tests
3
+ *
4
+ * Tests for product service layer business logic
5
+ */
6
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
7
+ import { ProductService } from '../service.js';
8
+ import { mockCredentials, mockProduct, mockProductMultipleVariants, mockVariant } from '../../../__tests__/test-utils.js';
9
+ import * as shopifyClient from '../../../lib/shopify-client.js';
10
+ // Mock the shopify-client module
11
+ vi.mock('../../../lib/shopify-client.js', () => ({
12
+ shopifyGraphQL: vi.fn()
13
+ }));
14
+ describe('ProductService', () => {
15
+ beforeEach(() => {
16
+ vi.clearAllMocks();
17
+ });
18
+ describe('getProductById', () => {
19
+ it('should fetch product by ID', async () => {
20
+ const mockResponse = {
21
+ data: {
22
+ product: mockProduct
23
+ }
24
+ };
25
+ vi.mocked(shopifyClient.shopifyGraphQL).mockResolvedValue(mockResponse);
26
+ const result = await ProductService.getProductById(mockCredentials, '123456789');
27
+ expect(result).toEqual(mockProduct);
28
+ expect(shopifyClient.shopifyGraphQL).toHaveBeenCalledWith(mockCredentials, expect.stringContaining('query getProduct'), { id: 'gid://shopify/Product/123456789' });
29
+ });
30
+ it('should throw error if product not found', async () => {
31
+ const mockResponse = {
32
+ data: {
33
+ product: null
34
+ }
35
+ };
36
+ vi.mocked(shopifyClient.shopifyGraphQL).mockResolvedValue(mockResponse);
37
+ await expect(ProductService.getProductById(mockCredentials, '123456789')).rejects.toThrow('Product not found');
38
+ });
39
+ it('should throw error on GraphQL errors', async () => {
40
+ const mockResponse = {
41
+ data: null,
42
+ errors: [{ message: 'Invalid ID' }]
43
+ };
44
+ vi.mocked(shopifyClient.shopifyGraphQL).mockResolvedValue(mockResponse);
45
+ await expect(ProductService.getProductById(mockCredentials, '123456789')).rejects.toThrow('GraphQL error');
46
+ });
47
+ });
48
+ describe('updateDescription', () => {
49
+ it('should update description with plain text (wrapped in <p> tags)', async () => {
50
+ const mockUpdateResponse = {
51
+ data: {
52
+ productUpdate: {
53
+ product: { id: mockProduct.id, title: mockProduct.title, descriptionHtml: '<p>New description</p>' },
54
+ userErrors: []
55
+ }
56
+ }
57
+ };
58
+ const mockProductResponse = {
59
+ data: {
60
+ product: { ...mockProduct, descriptionHtml: '<p>New description</p>' }
61
+ }
62
+ };
63
+ vi.mocked(shopifyClient.shopifyGraphQL)
64
+ .mockResolvedValueOnce(mockUpdateResponse)
65
+ .mockResolvedValueOnce(mockProductResponse);
66
+ const result = await ProductService.updateDescription(mockCredentials, {
67
+ id: '123456789',
68
+ description: 'New description',
69
+ isHtml: false
70
+ });
71
+ expect(result.success).toBe(true);
72
+ expect(shopifyClient.shopifyGraphQL).toHaveBeenCalledWith(mockCredentials, expect.stringContaining('mutation productUpdate'), expect.objectContaining({
73
+ input: expect.objectContaining({
74
+ descriptionHtml: '<p>New description</p>'
75
+ })
76
+ }));
77
+ });
78
+ it('should update description with HTML (no wrapping)', async () => {
79
+ const htmlContent = '<h1>Title</h1><p>Content</p>';
80
+ const mockUpdateResponse = {
81
+ data: {
82
+ productUpdate: {
83
+ product: { id: mockProduct.id, title: mockProduct.title, descriptionHtml: htmlContent },
84
+ userErrors: []
85
+ }
86
+ }
87
+ };
88
+ const mockProductResponse = {
89
+ data: {
90
+ product: { ...mockProduct, descriptionHtml: htmlContent }
91
+ }
92
+ };
93
+ vi.mocked(shopifyClient.shopifyGraphQL)
94
+ .mockResolvedValueOnce(mockUpdateResponse)
95
+ .mockResolvedValueOnce(mockProductResponse);
96
+ const result = await ProductService.updateDescription(mockCredentials, {
97
+ id: '123456789',
98
+ description: htmlContent,
99
+ isHtml: true
100
+ });
101
+ expect(result.success).toBe(true);
102
+ expect(shopifyClient.shopifyGraphQL).toHaveBeenCalledWith(mockCredentials, expect.stringContaining('mutation productUpdate'), expect.objectContaining({
103
+ input: expect.objectContaining({
104
+ descriptionHtml: htmlContent
105
+ })
106
+ }));
107
+ });
108
+ it('should handle user errors', async () => {
109
+ const mockResponse = {
110
+ data: {
111
+ productUpdate: {
112
+ product: { id: mockProduct.id, title: mockProduct.title },
113
+ userErrors: [{ field: ['description'], message: 'Description is invalid' }]
114
+ }
115
+ }
116
+ };
117
+ vi.mocked(shopifyClient.shopifyGraphQL).mockResolvedValue(mockResponse);
118
+ const result = await ProductService.updateDescription(mockCredentials, {
119
+ id: '123456789',
120
+ description: 'Test'
121
+ });
122
+ expect(result.success).toBe(false);
123
+ expect(result.error).toContain('Description is invalid');
124
+ });
125
+ });
126
+ describe('updatePrice', () => {
127
+ it('should update price using bulk mutation', async () => {
128
+ const mockProductResponse = {
129
+ data: {
130
+ product: mockProduct
131
+ }
132
+ };
133
+ const mockUpdateResponse = {
134
+ data: {
135
+ productVariantsBulkUpdate: {
136
+ productVariants: [
137
+ {
138
+ id: mockVariant.id,
139
+ price: '34.99',
140
+ compareAtPrice: null
141
+ }
142
+ ],
143
+ userErrors: []
144
+ }
145
+ }
146
+ };
147
+ vi.mocked(shopifyClient.shopifyGraphQL)
148
+ .mockResolvedValueOnce(mockProductResponse)
149
+ .mockResolvedValueOnce(mockUpdateResponse)
150
+ .mockResolvedValueOnce(mockProductResponse);
151
+ const result = await ProductService.updatePrice(mockCredentials, {
152
+ id: '123456789',
153
+ price: '34.99'
154
+ });
155
+ expect(result.success).toBe(true);
156
+ expect(shopifyClient.shopifyGraphQL).toHaveBeenCalledWith(mockCredentials, expect.stringContaining('productVariantsBulkUpdate'), expect.objectContaining({
157
+ productId: 'gid://shopify/Product/123456789',
158
+ variants: expect.arrayContaining([
159
+ expect.objectContaining({
160
+ price: '34.99'
161
+ })
162
+ ])
163
+ }));
164
+ });
165
+ it('should update price with compare-at price', async () => {
166
+ const mockProductResponse = {
167
+ data: {
168
+ product: mockProduct
169
+ }
170
+ };
171
+ const mockUpdateResponse = {
172
+ data: {
173
+ productVariantsBulkUpdate: {
174
+ productVariants: [
175
+ {
176
+ id: mockVariant.id,
177
+ price: '24.99',
178
+ compareAtPrice: '39.99'
179
+ }
180
+ ],
181
+ userErrors: []
182
+ }
183
+ }
184
+ };
185
+ vi.mocked(shopifyClient.shopifyGraphQL)
186
+ .mockResolvedValueOnce(mockProductResponse)
187
+ .mockResolvedValueOnce(mockUpdateResponse)
188
+ .mockResolvedValueOnce(mockProductResponse);
189
+ const result = await ProductService.updatePrice(mockCredentials, {
190
+ id: '123456789',
191
+ price: '24.99',
192
+ compareAtPrice: '39.99'
193
+ });
194
+ expect(result.success).toBe(true);
195
+ expect(shopifyClient.shopifyGraphQL).toHaveBeenCalledWith(mockCredentials, expect.stringContaining('productVariantsBulkUpdate'), expect.objectContaining({
196
+ variants: expect.arrayContaining([
197
+ expect.objectContaining({
198
+ price: '24.99',
199
+ compareAtPrice: '39.99'
200
+ })
201
+ ])
202
+ }));
203
+ });
204
+ it('should fail with multiple variants without variant-id', async () => {
205
+ const mockResponse = {
206
+ data: {
207
+ product: mockProductMultipleVariants
208
+ }
209
+ };
210
+ vi.mocked(shopifyClient.shopifyGraphQL).mockResolvedValue(mockResponse);
211
+ const result = await ProductService.updatePrice(mockCredentials, {
212
+ id: '123456789',
213
+ price: '29.99'
214
+ });
215
+ expect(result.success).toBe(false);
216
+ expect(result.error).toContain('multiple variants');
217
+ expect(result.error).toContain('--variant-id');
218
+ });
219
+ it('should validate price format', async () => {
220
+ const result = await ProductService.updatePrice(mockCredentials, {
221
+ id: '123456789',
222
+ price: 'invalid'
223
+ });
224
+ expect(result.success).toBe(false);
225
+ expect(result.error).toContain('Invalid price format');
226
+ });
227
+ it('should validate compare-at price format', async () => {
228
+ const result = await ProductService.updatePrice(mockCredentials, {
229
+ id: '123456789',
230
+ price: '29.99',
231
+ compareAtPrice: 'invalid'
232
+ });
233
+ expect(result.success).toBe(false);
234
+ expect(result.error).toContain('Invalid compare-at price format');
235
+ });
236
+ });
237
+ describe('updateTemplate', () => {
238
+ it('should update template suffix', async () => {
239
+ const mockUpdateResponse = {
240
+ data: {
241
+ productUpdate: {
242
+ product: {
243
+ id: mockProduct.id,
244
+ title: mockProduct.title,
245
+ templateSuffix: 'premium'
246
+ },
247
+ userErrors: []
248
+ }
249
+ }
250
+ };
251
+ const mockProductResponse = {
252
+ data: {
253
+ product: { ...mockProduct, templateSuffix: 'premium' }
254
+ }
255
+ };
256
+ vi.mocked(shopifyClient.shopifyGraphQL)
257
+ .mockResolvedValueOnce(mockUpdateResponse)
258
+ .mockResolvedValueOnce(mockProductResponse);
259
+ const result = await ProductService.updateTemplate(mockCredentials, {
260
+ id: '123456789',
261
+ template: 'premium'
262
+ });
263
+ expect(result.success).toBe(true);
264
+ expect(shopifyClient.shopifyGraphQL).toHaveBeenCalledWith(mockCredentials, expect.stringContaining('mutation productUpdate'), expect.objectContaining({
265
+ input: expect.objectContaining({
266
+ templateSuffix: 'premium'
267
+ })
268
+ }));
269
+ });
270
+ it('should clear template (set to null)', async () => {
271
+ const mockUpdateResponse = {
272
+ data: {
273
+ productUpdate: {
274
+ product: {
275
+ id: mockProduct.id,
276
+ title: mockProduct.title,
277
+ templateSuffix: null
278
+ },
279
+ userErrors: []
280
+ }
281
+ }
282
+ };
283
+ const mockProductResponse = {
284
+ data: {
285
+ product: { ...mockProduct, templateSuffix: null }
286
+ }
287
+ };
288
+ vi.mocked(shopifyClient.shopifyGraphQL)
289
+ .mockResolvedValueOnce(mockUpdateResponse)
290
+ .mockResolvedValueOnce(mockProductResponse);
291
+ const result = await ProductService.updateTemplate(mockCredentials, {
292
+ id: '123456789',
293
+ template: 'default'
294
+ });
295
+ expect(result.success).toBe(true);
296
+ });
297
+ });
298
+ });
299
+ //# sourceMappingURL=service.test.js.map