diff --git a/cmd/cmd.go b/cmd/cmd.go index 6969c7d..16e82b8 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -39,6 +39,7 @@ func NewCommad() *cobra.Command { } command.AddCommand(NewImportCommand(&clientOpts)) + command.AddCommand(NewImportDirCommand(&clientOpts)) command.AddCommand(NewVersionCommand()) command.AddCommand(NewTestCommand()) command.AddCommand(NewImportURLCommand()) diff --git a/cmd/importDir.go b/cmd/importDir.go new file mode 100644 index 0000000..678aa2c --- /dev/null +++ b/cmd/importDir.go @@ -0,0 +1,324 @@ +/* + * Copyright The Microcks Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/microcks/microcks-cli/pkg/config" + "github.com/microcks/microcks-cli/pkg/connectors" + "github.com/spf13/cobra" +) + +// MicrocksClient interface for dependency injection +type MicrocksClient interface { + UploadArtifact(file string, main bool) (string, error) +} + +type FileType struct { + Extension string + IsPrimary bool +} + +type ImportResult struct { + TotalFiles int + SuccessCount int + FailedCount int + SuccessFiles []string + FailedFiles []string + Errors []string +} + +type ImportConfig struct { + Recursive bool + Pattern string + Verbose bool +} + +type FileSystem interface { + Stat(path string) (os.FileInfo, error) + Walk(root string, walkFn filepath.WalkFunc) error + ReadDir(name string) ([]os.DirEntry, error) +} + +type RealFileSystem struct{} + +func (fs *RealFileSystem) Stat(path string) (os.FileInfo, error) { + return os.Stat(path) +} + +func (fs *RealFileSystem) Walk(root string, walkFn filepath.WalkFunc) error { + return filepath.Walk(root, walkFn) +} + +func (fs *RealFileSystem) ReadDir(name string) ([]os.DirEntry, error) { + return os.ReadDir(name) +} + +var supportedExtensions = map[string]bool{ + ".yaml": true, + ".yml": true, + ".json": true, + ".xml": true, +} + +type ImportError struct { + File string + Err error +} + +type ValidationError struct { + Message string +} + +func (e ImportError) Error() string { + return fmt.Sprintf("failed to import %s: %v", e.File, e.Err) +} + +func (e ValidationError) Error() string { + return e.Message +} + +func NewImportDirCommand(globalClientOpts *connectors.ClientOptions) *cobra.Command { + var ( + recursive bool + pattern string + verbose bool + ) + + var importDirCmd = &cobra.Command{ + Use: "import-dir", + Short: "Import API artifacts from a directory", + Long: `Import API artifacts from a directory recursively. + + This command scans a directory for API specification files and imports them into Microcks. + Supported file types: .yaml, .yml, .json, .xml + + Examples: + microcks import-dir ./api-specs + microcks import-dir ./api-specs --recursive + microcks import-dir ./api-specs --pattern "*.yaml" + microcks import-dir ./api-specs --recursive --pattern "openapi.*"`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + fmt.Println("import-dir command requires a directory path") + cmd.HelpFunc()(cmd, args) + os.Exit(1) + } + + dirPath := args[0] + + config.InsecureTLS = globalClientOpts.InsecureTLS + config.CaCertPaths = globalClientOpts.CaCertPaths + config.Verbose = globalClientOpts.Verbose + + localConfig, err := config.ReadLocalConfig(globalClientOpts.ConfigPath) + if err != nil { + fmt.Println(err) + return + } + + if localConfig == nil { + fmt.Println("Please login to perform operation...") + return + } + + if globalClientOpts.Context == "" { + globalClientOpts.Context = localConfig.CurrentContext + } + + // Create client + mc, err := connectors.NewClient(*globalClientOpts) + if err != nil { + fmt.Printf("error %v", err) + return + } + + // Set up business logic dependencies + fs := &RealFileSystem{} + importConfig := ImportConfig{ + Recursive: recursive, + Pattern: pattern, + Verbose: verbose, + } + + // Execute business logic + result, err := ImportDirectory(mc, fs, dirPath, importConfig) + if err != nil { + if validationErr, ok := err.(*ValidationError); ok { + fmt.Println(validationErr.Message) + return + } + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + // Display results + if verbose { + fmt.Printf("Found %d specification files to import...\n", result.TotalFiles) + for i, file := range result.SuccessFiles { + fmt.Printf("[%d/%d] โœ“ Imported: %s\n", i+1, result.TotalFiles, file) + } + for i, file := range result.FailedFiles { + errorMsg := "Unknown error" + if i < len(result.Errors) { + errorMsg = result.Errors[i] + } + fmt.Printf("โœ— Failed: %s - %s\n", file, errorMsg) + } + } else { + for _, file := range result.SuccessFiles { + fmt.Printf("โœ“ Imported: %s\n", file) + } + for i, file := range result.FailedFiles { + errorMsg := "Unknown error" + if i < len(result.Errors) { + errorMsg = result.Errors[i] + } + fmt.Printf("โœ— Failed: %s - %s\n", file, errorMsg) + } + } + + fmt.Printf("\nImport completed: %d/%d files imported successfully\n", result.SuccessCount, result.TotalFiles) + }, + } + + importDirCmd.Flags().BoolVar(&recursive, "recursive", false, "Scan subdirectories recursively") + importDirCmd.Flags().StringVar(&pattern, "pattern", "", "File pattern to match (e.g., '*.yaml', 'openapi.*')") + importDirCmd.Flags().BoolVar(&verbose, "verbose", false, "Show detailed progress") + + return importDirCmd +} + +func ImportDirectory(client MicrocksClient, fs FileSystem, dirPath string, config ImportConfig) (ImportResult, error) { + if err := validateDirectory(fs, dirPath); err != nil { + return ImportResult{}, err + } + + files, err := findSpecificationFiles(fs, dirPath, config.Recursive, config.Pattern) + if err != nil { + return ImportResult{}, fmt.Errorf("error scanning directory: %w", err) + } + + if len(files) == 0 { + return ImportResult{}, &ValidationError{Message: fmt.Sprintf("no specification files found in directory: %s", dirPath)} + } + + result := ImportResult{ + TotalFiles: len(files), + SuccessFiles: make([]string, 0, len(files)), + FailedFiles: make([]string, 0, len(files)), + Errors: make([]string, 0, len(files)), + } + + for _, file := range files { + fileType := detectFileType(file) + + _, err := client.UploadArtifact(file, fileType.IsPrimary) + if err != nil { + result.FailedCount++ + result.FailedFiles = append(result.FailedFiles, file) + result.Errors = append(result.Errors, fmt.Sprintf("error importing %s: %v", file, err)) + continue + } + + result.SuccessCount++ + result.SuccessFiles = append(result.SuccessFiles, file) + } + + return result, nil +} + +// validateDirectory checks if the directory exists and is accessible +func validateDirectory(fs FileSystem, dirPath string) error { + info, err := fs.Stat(dirPath) + if err != nil { + if os.IsNotExist(err) { + return &ValidationError{Message: fmt.Sprintf("directory does not exist: %s", dirPath)} + } + return fmt.Errorf("error accessing directory %s: %w", dirPath, err) + } + + if !info.IsDir() { + return &ValidationError{Message: fmt.Sprintf("path is not a directory: %s", dirPath)} + } + + return nil +} + +func findSpecificationFiles(fs FileSystem, dirPath string, recursive bool, pattern string) ([]string, error) { + var files []string + + walkFunc := func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() && !recursive && path != dirPath { + return filepath.SkipDir + } + + if info.IsDir() { + return nil + } + + ext := strings.ToLower(filepath.Ext(path)) + if !supportedExtensions[ext] { + return nil + } + + if pattern != "" { + matched, err := filepath.Match(pattern, filepath.Base(path)) + if err != nil { + return err + } + if !matched { + return nil + } + } + + files = append(files, path) + return nil + } + + err := fs.Walk(dirPath, walkFunc) + return files, err +} + +func detectFileType(filePath string) FileType { + fileName := strings.ToLower(filepath.Base(filePath)) + ext := filepath.Ext(filePath) + + // Default to primary for most files + isPrimary := true + + if strings.Contains(fileName, "postman") || strings.Contains(fileName, "collection") { + isPrimary = false + } + + // If there's an OpenAPI file, prefer it as primary + if strings.Contains(fileName, "openapi") || strings.Contains(fileName, "swagger") { + isPrimary = true + } + + return FileType{ + Extension: ext, + IsPrimary: isPrimary, + } +} diff --git a/cmd/importDir_test.go b/cmd/importDir_test.go new file mode 100644 index 0000000..5693880 --- /dev/null +++ b/cmd/importDir_test.go @@ -0,0 +1,423 @@ +/* + * Copyright The Microcks Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + "testing" + "time" + + "github.com/microcks/microcks-cli/pkg/connectors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type MockMicrocksClient struct { + Uploaded []string + FailedFiles map[string]error + UploadCalls int +} + +func (m *MockMicrocksClient) UploadArtifact(file string, main bool) (string, error) { + m.UploadCalls++ + m.Uploaded = append(m.Uploaded, file) + + if err, exists := m.FailedFiles[file]; exists { + return "", err + } + return fmt.Sprintf("mock-id-%d", m.UploadCalls), nil +} + +type MockFileSystem struct { + Files map[string]bool // path -> isDir + StatErrors map[string]error + WalkErrors map[string]error +} + +func (m *MockFileSystem) Stat(path string) (os.FileInfo, error) { + if err, exists := m.StatErrors[path]; exists { + return nil, err + } + + isDir, exists := m.Files[path] + if !exists { + return nil, os.ErrNotExist + } + + return &MockFileInfo{name: filepath.Base(path), isDir: isDir}, nil +} + +func (m *MockFileSystem) Walk(root string, walkFn filepath.WalkFunc) error { + if err, exists := m.WalkErrors[root]; exists { + return err + } + + for path, isDir := range m.Files { + if filepath.HasPrefix(path, root) { + info := &MockFileInfo{name: filepath.Base(path), isDir: isDir} + if err := walkFn(path, info, nil); err != nil { + return err + } + } + } + return nil +} + +func (m *MockFileSystem) ReadDir(name string) ([]os.DirEntry, error) { + // Not used in our current implementation, but required by interface + return nil, nil +} + +type MockFileInfo struct { + name string + isDir bool +} + +func (m *MockFileInfo) Name() string { return m.name } +func (m *MockFileInfo) Size() int64 { return 0 } +func (m *MockFileInfo) Mode() os.FileMode { return 0 } +func (m *MockFileInfo) ModTime() time.Time { return time.Time{} } +func (m *MockFileInfo) IsDir() bool { return m.isDir } +func (m *MockFileInfo) Sys() interface{} { return nil } + +func TestImportDirectory(t *testing.T) { + tests := []struct { + name string + files map[string]bool // path -> isDir + failedFiles map[string]error + config ImportConfig + expectedResult ImportResult + expectError bool + }{ + { + name: "successful import of all files", + files: map[string]bool{ + "/test": true, // Directory must exist + "/test/openapi.yaml": false, + "/test/postman.json": false, + }, + config: ImportConfig{Recursive: false, Pattern: ""}, + expectedResult: ImportResult{ + TotalFiles: 2, + SuccessCount: 2, + FailedCount: 0, + SuccessFiles: []string{"/test/openapi.yaml", "/test/postman.json"}, + FailedFiles: []string{}, + Errors: []string{}, + }, + }, + { + name: "partial failure", + files: map[string]bool{ + "/test": true, // Directory must exist + "/test/openapi.yaml": false, + "/test/postman.json": false, + }, + failedFiles: map[string]error{ + "/test/postman.json": fmt.Errorf("upload failed"), + }, + config: ImportConfig{Recursive: false, Pattern: ""}, + expectedResult: ImportResult{ + TotalFiles: 2, + SuccessCount: 1, + FailedCount: 1, + SuccessFiles: []string{"/test/openapi.yaml"}, + FailedFiles: []string{"/test/postman.json"}, + Errors: []string{"error importing /test/postman.json: upload failed"}, + }, + }, + { + name: "recursive scan", + files: map[string]bool{ + "/test": true, // Directory must exist + "/test/openapi.yaml": false, + "/test/subdir/spec.yml": false, + "/test/subdir": true, + }, + config: ImportConfig{Recursive: true, Pattern: ""}, + expectedResult: ImportResult{ + TotalFiles: 2, + SuccessCount: 2, + FailedCount: 0, + SuccessFiles: []string{"/test/openapi.yaml", "/test/subdir/spec.yml"}, + FailedFiles: []string{}, + Errors: []string{}, + }, + }, + { + name: "pattern filtering", + files: map[string]bool{ + "/test": true, // Directory must exist + "/test/openapi.yaml": false, + "/test/postman.json": false, + }, + config: ImportConfig{Recursive: false, Pattern: "*.yaml"}, + expectedResult: ImportResult{ + TotalFiles: 1, + SuccessCount: 1, + FailedCount: 0, + SuccessFiles: []string{"/test/openapi.yaml"}, + FailedFiles: []string{}, + Errors: []string{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mocks + mockClient := &MockMicrocksClient{ + FailedFiles: tt.failedFiles, + } + mockFS := &MockFileSystem{ + Files: tt.files, + } + + // Execute + result, err := ImportDirectory(mockClient, mockFS, "/test", tt.config) + + // Assertions + if tt.expectError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.expectedResult.TotalFiles, result.TotalFiles) + assert.Equal(t, tt.expectedResult.SuccessCount, result.SuccessCount) + assert.Equal(t, tt.expectedResult.FailedCount, result.FailedCount) + assert.ElementsMatch(t, tt.expectedResult.SuccessFiles, result.SuccessFiles) + assert.ElementsMatch(t, tt.expectedResult.FailedFiles, result.FailedFiles) + }) + } +} + +// TestValidateDirectory tests directory validation +func TestValidateDirectory(t *testing.T) { + tests := []struct { + name string + files map[string]bool + statErrors map[string]error + expectError bool + errorType string + }{ + { + name: "valid directory", + files: map[string]bool{ + "/test": true, + }, + expectError: false, + }, + { + name: "directory does not exist", + statErrors: map[string]error{ + "/test": os.ErrNotExist, + }, + expectError: true, + errorType: "ValidationError", + }, + { + name: "path is not a directory", + files: map[string]bool{ + "/test": false, + }, + expectError: true, + errorType: "ValidationError", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockFS := &MockFileSystem{ + Files: tt.files, + StatErrors: tt.statErrors, + } + + err := validateDirectory(mockFS, "/test") + + if tt.expectError { + assert.Error(t, err) + if tt.errorType == "ValidationError" { + _, ok := err.(*ValidationError) + assert.True(t, ok, "Expected ValidationError") + } + } else { + assert.NoError(t, err) + } + }) + } +} + +// TestFindSpecificationFilesWithFS tests file discovery with mock filesystem +func TestFindSpecificationFilesWithFS(t *testing.T) { + tests := []struct { + name string + files map[string]bool + recursive bool + pattern string + expected []string + }{ + { + name: "non-recursive scan", + files: map[string]bool{ + "/test": true, // Directory must exist + "/test/openapi.yaml": false, + "/test/postman.json": false, + "/test/ignore.txt": false, + }, + recursive: false, + expected: []string{"/test/openapi.yaml", "/test/postman.json"}, + }, + { + name: "recursive scan", + files: map[string]bool{ + "/test": true, // Directory must exist + "/test/openapi.yaml": false, + "/test/subdir": true, + "/test/subdir/spec.yml": false, + }, + recursive: true, + expected: []string{"/test/openapi.yaml", "/test/subdir/spec.yml"}, + }, + { + name: "pattern filtering", + files: map[string]bool{ + "/test": true, // Directory must exist + "/test/openapi.yaml": false, + "/test/postman.json": false, + }, + recursive: false, + pattern: "*.yaml", + expected: []string{"/test/openapi.yaml"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockFS := &MockFileSystem{Files: tt.files} + + files, err := findSpecificationFiles(mockFS, "/test", tt.recursive, tt.pattern) + + assert.NoError(t, err) + assert.ElementsMatch(t, tt.expected, files) + }) + } +} + +// TestDetectFileTypeWithLogic tests file type detection +func TestDetectFileTypeWithLogic(t *testing.T) { + tests := []struct { + filePath string + expected FileType + }{ + { + filePath: "openapi.yaml", + expected: FileType{Extension: ".yaml", IsPrimary: true}, + }, + { + filePath: "swagger.json", + expected: FileType{Extension: ".json", IsPrimary: true}, + }, + { + filePath: "postman-collection.json", + expected: FileType{Extension: ".json", IsPrimary: false}, + }, + { + filePath: "my-collection.json", + expected: FileType{Extension: ".json", IsPrimary: false}, + }, + { + filePath: "api-spec.yml", + expected: FileType{Extension: ".yml", IsPrimary: true}, + }, + } + + for _, tt := range tests { + t.Run(tt.filePath, func(t *testing.T) { + result := detectFileType(tt.filePath) + assert.Equal(t, tt.expected.Extension, result.Extension) + assert.Equal(t, tt.expected.IsPrimary, result.IsPrimary) + }) + } +} + +// TestNewImportDirCommand tests command creation +func TestNewImportDirCommand(t *testing.T) { + clientOpts := &connectors.ClientOptions{} + cmd := NewImportDirCommand(clientOpts) + + // Test command properties + assert.Equal(t, "import-dir", cmd.Use) + assert.Equal(t, "Import API artifacts from a directory", cmd.Short) + assert.Contains(t, cmd.Long, "Supported file types") + + // Test that flags are properly defined + recursiveFlag := cmd.Flags().Lookup("recursive") + assert.NotNil(t, recursiveFlag) + assert.Equal(t, "bool", recursiveFlag.Value.Type()) + + patternFlag := cmd.Flags().Lookup("pattern") + assert.NotNil(t, patternFlag) + assert.Equal(t, "string", patternFlag.Value.Type()) + + verboseFlag := cmd.Flags().Lookup("verbose") + assert.NotNil(t, verboseFlag) + assert.Equal(t, "bool", verboseFlag.Value.Type()) +} + +// TestImportResult tests the ImportResult struct +func TestImportResult(t *testing.T) { + result := ImportResult{ + TotalFiles: 5, + SuccessCount: 3, + FailedCount: 2, + SuccessFiles: []string{"a.yaml", "b.json", "c.yml"}, + FailedFiles: []string{"d.txt", "e.pdf"}, + Errors: []string{"error 1", "error 2"}, + } + + assert.Equal(t, 5, result.TotalFiles) + assert.Equal(t, 3, result.SuccessCount) + assert.Equal(t, 2, result.FailedCount) + assert.Len(t, result.SuccessFiles, 3) + assert.Len(t, result.FailedFiles, 2) + assert.Len(t, result.Errors, 2) +} + +// Benchmark tests for performance +func BenchmarkImportDirectory(b *testing.B) { + // Create a mock with many files + mockClient := &MockMicrocksClient{} + mockFS := &MockFileSystem{ + Files: make(map[string]bool), + } + + // Add 100 test files + for i := 0; i < 100; i++ { + path := fmt.Sprintf("/test/spec_%d.yaml", i) + mockFS.Files[path] = false + } + + config := ImportConfig{Recursive: false, Pattern: ""} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := ImportDirectory(mockClient, mockFS, "/test", config) + require.NoError(b, err) + } +} diff --git a/documentation/cmd/importDir.md b/documentation/cmd/importDir.md new file mode 100644 index 0000000..f746a33 --- /dev/null +++ b/documentation/cmd/importDir.md @@ -0,0 +1,76 @@ +# ImportDir Command + +The `import-dir` command in Microcks CLI is used to import multiple API specification artifacts from a directory into a Microcks server. + +๐Ÿ“ Description + +The `import-dir` command provides a convenient way to bulk import API specifications from a local directory. It automatically scans for supported file types and imports them into Microcks. This is particularly useful for CI/CD pipelines, bulk operations, and managing large collections of API specifications. + +๐Ÿ“Œ Usage +```bash +microcks import-dir [flags] +``` + +Arguments +- ``: Path to the directory containing API specification files + +| Flag | Type | Required | Description | +|-------------------------|---------|----------|-----------------------------------------------------------------------------| +| `--recursive` | bool | โŒ | Scan subdirectories recursively (default false) | +| `--pattern` | string | โŒ | File pattern to match (e.g., '*.yaml', 'openapi.*') | +| `--verbose` | bool | โŒ | Show detailed progress during import | + +๐Ÿงช Examples + +- Basic Directory Import +```bash +microcks import-dir ./api-specs +``` + +- Recursive Import with Verbose Output +```bash +microcks import-dir ./api-specs --recursive --verbose +``` + +- Import Only YAML Files +```bash +microcks import-dir ./api-specs --pattern "*.yaml" +``` + +- Import OpenAPI Files Recursively +```bash +microcks import-dir ./api-specs --recursive --pattern "openapi.*" +``` + +๐Ÿ“‹ Supported File Types + +The command automatically detects and imports the following file types: +- `.yaml` / `.yml` - OpenAPI, AsyncAPI, and other YAML-based specifications +- `.json` - OpenAPI, AsyncAPI, Postman collections, and other JSON-based specifications +- `.xml` - SOAP WSDL and other XML-based specifications + +๐Ÿ” File Type Detection + +The command automatically determines which files should be marked as primary artifacts: +- Files containing "openapi" or "swagger" in the filename are marked as primary +- Files containing "postman" or "collection" in the filename are marked as secondary +- All other files default to primary + +๐Ÿ“Š Output + +The command provides: +- Progress reporting showing which files are being imported +- Success/failure status for each file +- Summary of total files found and successfully imported +- Detailed error messages for failed imports + +๐Ÿ’ก Use Cases + +- **CI/CD Pipelines**: Automatically import API specs during build processes +- **Bulk Operations**: Import large collections of API specifications +- **Development Workflows**: Import specs from development directories +- **Testing**: Import test specifications for validation + +๐Ÿ”ง Integration + +This command integrates with the existing Microcks CLI context system and authentication, making it compatible with all existing workflows and configurations. \ No newline at end of file diff --git a/samples/ecommerce-api-openapi.yml b/samples/ecommerce-api-openapi.yml new file mode 100644 index 0000000..14eb60f --- /dev/null +++ b/samples/ecommerce-api-openapi.yml @@ -0,0 +1,805 @@ +--- +openapi: 3.0.3 +info: + title: E-Commerce Platform API + version: 2.0.0 + description: A comprehensive e-commerce platform API with product management, user management, orders, and payment processing + contact: + name: API Team + url: https://api.example.com + email: api@example.com + license: + name: MIT License + url: https://opensource.org/licenses/MIT +servers: + - url: https://api.example.com/v2 + description: Production server + - url: https://staging-api.example.com/v2 + description: Staging server +paths: + /products: + get: + operationId: GetProducts + summary: Get all products with pagination and filtering + parameters: + - name: page + in: query + description: Page number for pagination + required: false + schema: + type: integer + default: 1 + minimum: 1 + - name: limit + in: query + description: Number of products per page + required: false + schema: + type: integer + default: 20 + maximum: 100 + - name: category + in: query + description: Filter by category + required: false + schema: + type: string + - name: minPrice + in: query + description: Minimum price filter + required: false + schema: + type: number + - name: maxPrice + in: query + description: Maximum price filter + required: false + schema: + type: number + - name: sortBy + in: query + description: Sort field + required: false + schema: + type: string + enum: [name, price, rating, createdAt] + - name: sortOrder + in: query + description: Sort order + required: false + schema: + type: string + enum: [asc, desc] + default: asc + responses: + "200": + description: List of products + content: + application/json: + schema: + $ref: "#/components/schemas/ProductList" + examples: + success: + summary: Successful response + value: + products: + - id: "prod_001" + name: "Wireless Bluetooth Headphones" + description: "High-quality wireless headphones with noise cancellation" + price: 199.99 + category: "Electronics" + brand: "TechAudio" + rating: 4.5 + stockQuantity: 150 + images: + - "https://example.com/images/headphones1.jpg" + - "https://example.com/images/headphones2.jpg" + specifications: + batteryLife: "20 hours" + connectivity: "Bluetooth 5.0" + weight: "250g" + createdAt: "2024-01-15T10:30:00Z" + updatedAt: "2024-01-20T14:45:00Z" + - id: "prod_002" + name: "Organic Cotton T-Shirt" + description: "Comfortable organic cotton t-shirt in various colors" + price: 29.99 + category: "Clothing" + brand: "EcoWear" + rating: 4.2 + stockQuantity: 300 + images: + - "https://example.com/images/tshirt1.jpg" + specifications: + material: "100% Organic Cotton" + sizes: ["S", "M", "L", "XL"] + colors: ["White", "Black", "Navy", "Gray"] + createdAt: "2024-01-10T09:15:00Z" + updatedAt: "2024-01-18T16:20:00Z" + pagination: + page: 1 + limit: 20 + total: 1250 + totalPages: 63 + hasNext: true + hasPrev: false + "400": + description: Bad request - invalid parameters + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal server error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + operationId: CreateProduct + summary: Create a new product + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ProductCreate" + examples: + newProduct: + summary: New product example + value: + name: "Smart Fitness Watch" + description: "Advanced fitness tracking with heart rate monitoring" + price: 299.99 + category: "Electronics" + brand: "FitTech" + stockQuantity: 75 + images: + - "https://example.com/images/watch1.jpg" + - "https://example.com/images/watch2.jpg" + specifications: + batteryLife: "7 days" + waterResistance: "5ATM" + connectivity: "Bluetooth, GPS" + responses: + "201": + description: Product created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/Product" + "400": + description: Bad request - invalid product data + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Forbidden - insufficient permissions + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /products/{productId}: + get: + operationId: GetProduct + summary: Get a specific product by ID + parameters: + - name: productId + in: path + required: true + description: The product ID + schema: + type: string + responses: + "200": + description: Product details + content: + application/json: + schema: + $ref: "#/components/schemas/Product" + examples: + detailedProduct: + summary: Detailed product information + value: + id: "prod_001" + name: "Wireless Bluetooth Headphones" + description: "High-quality wireless headphones with active noise cancellation technology. Features include 20-hour battery life, premium sound quality, and comfortable over-ear design." + price: 199.99 + category: "Electronics" + brand: "TechAudio" + rating: 4.5 + reviewCount: 1247 + stockQuantity: 150 + images: + - "https://example.com/images/headphones1.jpg" + - "https://example.com/images/headphones2.jpg" + - "https://example.com/images/headphones3.jpg" + specifications: + batteryLife: "20 hours" + connectivity: "Bluetooth 5.0" + weight: "250g" + dimensions: "18cm x 16cm x 8cm" + noiseCancellation: true + microphone: true + warranty: "2 years" + variants: + - color: "Black" + price: 199.99 + stockQuantity: 75 + - color: "White" + price: 199.99 + stockQuantity: 50 + - color: "Blue" + price: 219.99 + stockQuantity: 25 + reviews: + - id: "rev_001" + userId: "user_123" + rating: 5 + comment: "Excellent sound quality and very comfortable!" + createdAt: "2024-01-18T10:30:00Z" + - id: "rev_002" + userId: "user_456" + rating: 4 + comment: "Great headphones, battery life is impressive." + createdAt: "2024-01-17T14:20:00Z" + createdAt: "2024-01-15T10:30:00Z" + updatedAt: "2024-01-20T14:45:00Z" + "404": + description: Product not found + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /users: + get: + operationId: GetUsers + summary: Get all users with pagination + security: + - BearerAuth: [] + parameters: + - name: page + in: query + description: Page number for pagination + required: false + schema: + type: integer + default: 1 + - name: limit + in: query + description: Number of users per page + required: false + schema: + type: integer + default: 20 + maximum: 100 + - name: role + in: query + description: Filter by user role + required: false + schema: + type: string + enum: [customer, admin, moderator] + responses: + "200": + description: List of users + content: + application/json: + schema: + $ref: "#/components/schemas/UserList" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /orders: + get: + operationId: GetOrders + summary: Get user orders + security: + - BearerAuth: [] + parameters: + - name: page + in: query + description: Page number for pagination + required: false + schema: + type: integer + default: 1 + - name: limit + in: query + description: Number of orders per page + required: false + schema: + type: integer + default: 10 + - name: status + in: query + description: Filter by order status + required: false + schema: + type: string + enum: [pending, confirmed, shipped, delivered, cancelled] + responses: + "200": + description: List of orders + content: + application/json: + schema: + $ref: "#/components/schemas/OrderList" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + post: + operationId: CreateOrder + summary: Create a new order + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/OrderCreate" + responses: + "201": + description: Order created successfully + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + "400": + description: Bad request - invalid order data + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + + /payments: + post: + operationId: ProcessPayment + summary: Process a payment + security: + - BearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/PaymentRequest" + responses: + "200": + description: Payment processed successfully + content: + application/json: + schema: + $ref: "#/components/schemas/PaymentResponse" + "400": + description: Bad request - invalid payment data + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "402": + description: Payment failed + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + +components: + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + Product: + type: object + properties: + id: + type: string + description: Unique product identifier + name: + type: string + description: Product name + description: + type: string + description: Product description + price: + type: number + format: double + description: Product price + category: + type: string + description: Product category + brand: + type: string + description: Product brand + rating: + type: number + format: double + description: Average product rating + reviewCount: + type: integer + description: Number of reviews + stockQuantity: + type: integer + description: Available stock quantity + images: + type: array + items: + type: string + description: Product image URLs + specifications: + type: object + additionalProperties: true + description: Product specifications + variants: + type: array + items: + $ref: "#/components/schemas/ProductVariant" + description: Product variants + reviews: + type: array + items: + $ref: "#/components/schemas/Review" + description: Product reviews + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - name + - price + - category + + ProductCreate: + type: object + properties: + name: + type: string + description: + type: string + price: + type: number + category: + type: string + brand: + type: string + stockQuantity: + type: integer + images: + type: array + items: + type: string + specifications: + type: object + additionalProperties: true + required: + - name + - price + - category + + ProductList: + type: object + properties: + products: + type: array + items: + $ref: "#/components/schemas/Product" + pagination: + $ref: "#/components/schemas/Pagination" + + ProductVariant: + type: object + properties: + color: + type: string + price: + type: number + stockQuantity: + type: integer + required: + - color + - price + + Review: + type: object + properties: + id: + type: string + userId: + type: string + rating: + type: integer + minimum: 1 + maximum: 5 + comment: + type: string + createdAt: + type: string + format: date-time + required: + - id + - userId + - rating + + User: + type: object + properties: + id: + type: string + email: + type: string + format: email + firstName: + type: string + lastName: + type: string + role: + type: string + enum: [customer, admin, moderator] + isActive: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - email + - firstName + - lastName + - role + + UserList: + type: object + properties: + users: + type: array + items: + $ref: "#/components/schemas/User" + pagination: + $ref: "#/components/schemas/Pagination" + + Order: + type: object + properties: + id: + type: string + userId: + type: string + items: + type: array + items: + $ref: "#/components/schemas/OrderItem" + totalAmount: + type: number + status: + type: string + enum: [pending, confirmed, shipped, delivered, cancelled] + shippingAddress: + $ref: "#/components/schemas/Address" + paymentMethod: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + required: + - id + - userId + - items + - totalAmount + - status + + OrderCreate: + type: object + properties: + items: + type: array + items: + $ref: "#/components/schemas/OrderItemCreate" + shippingAddress: + $ref: "#/components/schemas/Address" + paymentMethod: + type: string + required: + - items + - shippingAddress + - paymentMethod + + OrderList: + type: object + properties: + orders: + type: array + items: + $ref: "#/components/schemas/Order" + pagination: + $ref: "#/components/schemas/Pagination" + + OrderItem: + type: object + properties: + productId: + type: string + productName: + type: string + quantity: + type: integer + unitPrice: + type: number + totalPrice: + type: number + required: + - productId + - quantity + - unitPrice + + OrderItemCreate: + type: object + properties: + productId: + type: string + quantity: + type: integer + required: + - productId + - quantity + + Address: + type: object + properties: + street: + type: string + city: + type: string + state: + type: string + zipCode: + type: string + country: + type: string + required: + - street + - city + - state + - zipCode + - country + + PaymentRequest: + type: object + properties: + orderId: + type: string + amount: + type: number + currency: + type: string + default: "USD" + paymentMethod: + type: string + enum: [credit_card, paypal, apple_pay, google_pay] + cardDetails: + $ref: "#/components/schemas/CardDetails" + required: + - orderId + - amount + - paymentMethod + + PaymentResponse: + type: object + properties: + transactionId: + type: string + status: + type: string + enum: [success, failed, pending] + amount: + type: number + currency: + type: string + processedAt: + type: string + format: date-time + required: + - transactionId + - status + - amount + + CardDetails: + type: object + properties: + cardNumber: + type: string + expiryMonth: + type: integer + minimum: 1 + maximum: 12 + expiryYear: + type: integer + cvv: + type: string + required: + - cardNumber + - expiryMonth + - expiryYear + - cvv + + Pagination: + type: object + properties: + page: + type: integer + limit: + type: integer + total: + type: integer + totalPages: + type: integer + hasNext: + type: boolean + hasPrev: + type: boolean + required: + - page + - limit + - total + - totalPages + + Error: + type: object + properties: + code: + type: string + message: + type: string + details: + type: object + additionalProperties: true + timestamp: + type: string + format: date-time + required: + - code + - message + - timestamp diff --git a/samples/ecommerce-api-postman.json b/samples/ecommerce-api-postman.json new file mode 100644 index 0000000..5059e1a --- /dev/null +++ b/samples/ecommerce-api-postman.json @@ -0,0 +1,717 @@ +{ + "info": { + "_postman_id": "c60c547f-69c3-45e7-b985-3f23439d4edf", + "name": "E-Commerce Platform API", + "description": "A comprehensive e-commerce platform API collection with product management, user management, orders, and payment processing", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "version": "2.0.0" + }, + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{authToken}}", + "type": "string" + } + ] + }, + "variable": [ + { + "key": "baseUrl", + "value": "https://api.example.com/v2", + "type": "string" + }, + { + "key": "authToken", + "value": "", + "type": "string" + }, + { + "key": "productId", + "value": "", + "type": "string" + }, + { + "key": "orderId", + "value": "", + "type": "string" + }, + { + "key": "userId", + "value": "", + "type": "string" + } + ], + "item": [ + { + "name": "Authentication", + "item": [ + { + "name": "Login", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"user@example.com\",\n \"password\": \"password123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"email\": \"user@example.com\",\n \"password\": \"password123\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/auth/login", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "auth", + "login" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...\",\n \"refreshToken\": \"refresh_token_here\",\n \"expiresIn\": 3600,\n \"user\": {\n \"id\": \"user_123\",\n \"email\": \"user@example.com\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"role\": \"customer\"\n }\n}" + } + ], + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "if (pm.response.code === 200) {", + " const response = pm.response.json();", + " pm.environment.set('authToken', response.token);", + " pm.collectionVariables.set('authToken', response.token);", + " pm.collectionVariables.set('userId', response.user.id);", + " console.log('Authentication successful');", + "}" + ], + "type": "text/javascript" + } + } + ] + } + ] + }, + { + "name": "Products", + "item": [ + { + "name": "Get All Products", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/products?page=1&limit=20&category=Electronics&sortBy=price&sortOrder=asc", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "products" + ], + "query": [ + { + "key": "page", + "value": "1", + "description": "Page number for pagination" + }, + { + "key": "limit", + "value": "20", + "description": "Number of products per page" + }, + { + "key": "category", + "value": "Electronics", + "description": "Filter by category" + }, + { + "key": "sortBy", + "value": "price", + "description": "Sort field" + }, + { + "key": "sortOrder", + "value": "asc", + "description": "Sort order" + } + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/products?page=1&limit=20", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "products" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"products\": [\n {\n \"id\": \"prod_001\",\n \"name\": \"Wireless Bluetooth Headphones\",\n \"description\": \"High-quality wireless headphones with noise cancellation\",\n \"price\": 199.99,\n \"category\": \"Electronics\",\n \"brand\": \"TechAudio\",\n \"rating\": 4.5,\n \"stockQuantity\": 150,\n \"images\": [\n \"https://example.com/images/headphones1.jpg\",\n \"https://example.com/images/headphones2.jpg\"\n ],\n \"specifications\": {\n \"batteryLife\": \"20 hours\",\n \"connectivity\": \"Bluetooth 5.0\",\n \"weight\": \"250g\"\n },\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"updatedAt\": \"2024-01-20T14:45:00Z\"\n },\n {\n \"id\": \"prod_002\",\n \"name\": \"Smart Fitness Watch\",\n \"description\": \"Advanced fitness tracking with heart rate monitoring\",\n \"price\": 299.99,\n \"category\": \"Electronics\",\n \"brand\": \"FitTech\",\n \"rating\": 4.3,\n \"stockQuantity\": 75,\n \"images\": [\n \"https://example.com/images/watch1.jpg\",\n \"https://example.com/images/watch2.jpg\"\n ],\n \"specifications\": {\n \"batteryLife\": \"7 days\",\n \"waterResistance\": \"5ATM\",\n \"connectivity\": \"Bluetooth, GPS\"\n },\n \"createdAt\": \"2024-01-12T08:15:00Z\",\n \"updatedAt\": \"2024-01-19T11:30:00Z\"\n }\n ],\n \"pagination\": {\n \"page\": 1,\n \"limit\": 20,\n \"total\": 1250,\n \"totalPages\": 63,\n \"hasNext\": true,\n \"hasPrev\": false\n }\n}" + } + ], + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Response has products array', function () {", + " const response = pm.response.json();", + " pm.expect(response).to.have.property('products');", + " pm.expect(response.products).to.be.an('array');", + "});", + "", + "pm.test('Response has pagination', function () {", + " const response = pm.response.json();", + " pm.expect(response).to.have.property('pagination');", + " pm.expect(response.pagination).to.have.property('page');", + " pm.expect(response.pagination).to.have.property('total');", + "});", + "", + "if (pm.response.json().products.length > 0) {", + " pm.collectionVariables.set('productId', pm.response.json().products[0].id);", + "}" + ], + "type": "text/javascript" + } + } + ] + }, + { + "name": "Get Product by ID", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/products/{{productId}}", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "products", + "{{productId}}" + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/products/prod_001", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "products", + "prod_001" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"id\": \"prod_001\",\n \"name\": \"Wireless Bluetooth Headphones\",\n \"description\": \"High-quality wireless headphones with active noise cancellation technology. Features include 20-hour battery life, premium sound quality, and comfortable over-ear design.\",\n \"price\": 199.99,\n \"category\": \"Electronics\",\n \"brand\": \"TechAudio\",\n \"rating\": 4.5,\n \"reviewCount\": 1247,\n \"stockQuantity\": 150,\n \"images\": [\n \"https://example.com/images/headphones1.jpg\",\n \"https://example.com/images/headphones2.jpg\",\n \"https://example.com/images/headphones3.jpg\"\n ],\n \"specifications\": {\n \"batteryLife\": \"20 hours\",\n \"connectivity\": \"Bluetooth 5.0\",\n \"weight\": \"250g\",\n \"dimensions\": \"18cm x 16cm x 8cm\",\n \"noiseCancellation\": true,\n \"microphone\": true,\n \"warranty\": \"2 years\"\n },\n \"variants\": [\n {\n \"color\": \"Black\",\n \"price\": 199.99,\n \"stockQuantity\": 75\n },\n {\n \"color\": \"White\",\n \"price\": 199.99,\n \"stockQuantity\": 50\n },\n {\n \"color\": \"Blue\",\n \"price\": 219.99,\n \"stockQuantity\": 25\n }\n ],\n \"reviews\": [\n {\n \"id\": \"rev_001\",\n \"userId\": \"user_123\",\n \"rating\": 5,\n \"comment\": \"Excellent sound quality and very comfortable!\",\n \"createdAt\": \"2024-01-18T10:30:00Z\"\n },\n {\n \"id\": \"rev_002\",\n \"userId\": \"user_456\",\n \"rating\": 4,\n \"comment\": \"Great headphones, battery life is impressive.\",\n \"createdAt\": \"2024-01-17T14:20:00Z\"\n }\n ],\n \"createdAt\": \"2024-01-15T10:30:00Z\",\n \"updatedAt\": \"2024-01-20T14:45:00Z\"\n}" + } + ], + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test('Product has required fields', function () {", + " const product = pm.response.json();", + " pm.expect(product).to.have.property('id');", + " pm.expect(product).to.have.property('name');", + " pm.expect(product).to.have.property('price');", + " pm.expect(product).to.have.property('category');", + "});", + "", + "pm.test('Product has reviews', function () {", + " const product = pm.response.json();", + " pm.expect(product).to.have.property('reviews');", + " pm.expect(product.reviews).to.be.an('array');", + "});" + ], + "type": "text/javascript" + } + } + ] + }, + { + "name": "Create Product", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Smart Fitness Watch\",\n \"description\": \"Advanced fitness tracking with heart rate monitoring\",\n \"price\": 299.99,\n \"category\": \"Electronics\",\n \"brand\": \"FitTech\",\n \"stockQuantity\": 75,\n \"images\": [\n \"https://example.com/images/watch1.jpg\",\n \"https://example.com/images/watch2.jpg\"\n ],\n \"specifications\": {\n \"batteryLife\": \"7 days\",\n \"waterResistance\": \"5ATM\",\n \"connectivity\": \"Bluetooth, GPS\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/products", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "products" + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Smart Fitness Watch\",\n \"description\": \"Advanced fitness tracking with heart rate monitoring\",\n \"price\": 299.99,\n \"category\": \"Electronics\",\n \"brand\": \"FitTech\",\n \"stockQuantity\": 75,\n \"images\": [\n \"https://example.com/images/watch1.jpg\",\n \"https://example.com/images/watch2.jpg\"\n ],\n \"specifications\": {\n \"batteryLife\": \"7 days\",\n \"waterResistance\": \"5ATM\",\n \"connectivity\": \"Bluetooth, GPS\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/products", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "products" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"id\": \"prod_003\",\n \"name\": \"Smart Fitness Watch\",\n \"description\": \"Advanced fitness tracking with heart rate monitoring\",\n \"price\": 299.99,\n \"category\": \"Electronics\",\n \"brand\": \"FitTech\",\n \"rating\": 0,\n \"reviewCount\": 0,\n \"stockQuantity\": 75,\n \"images\": [\n \"https://example.com/images/watch1.jpg\",\n \"https://example.com/images/watch2.jpg\"\n ],\n \"specifications\": {\n \"batteryLife\": \"7 days\",\n \"waterResistance\": \"5ATM\",\n \"connectivity\": \"Bluetooth, GPS\"\n },\n \"variants\": [],\n \"reviews\": [],\n \"createdAt\": \"2024-01-21T09:00:00Z\",\n \"updatedAt\": \"2024-01-21T09:00:00Z\"\n}" + } + ], + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 201', function () {", + " pm.response.to.have.status(201);", + "});", + "", + "pm.test('Product created successfully', function () {", + " const product = pm.response.json();", + " pm.expect(product).to.have.property('id');", + " pm.expect(product.name).to.eql('Smart Fitness Watch');", + " pm.expect(product.price).to.eql(299.99);", + "});" + ], + "type": "text/javascript" + } + } + ] + } + ] + }, + { + "name": "Users", + "item": [ + { + "name": "Get All Users", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/users?page=1&limit=20&role=customer", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + }, + { + "key": "role", + "value": "customer" + } + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/users?page=1&limit=20", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "users" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "20" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"users\": [\n {\n \"id\": \"user_123\",\n \"email\": \"john.doe@example.com\",\n \"firstName\": \"John\",\n \"lastName\": \"Doe\",\n \"role\": \"customer\",\n \"isActive\": true,\n \"createdAt\": \"2024-01-01T10:00:00Z\",\n \"updatedAt\": \"2024-01-15T14:30:00Z\"\n },\n {\n \"id\": \"user_456\",\n \"email\": \"jane.smith@example.com\",\n \"firstName\": \"Jane\",\n \"lastName\": \"Smith\",\n \"role\": \"customer\",\n \"isActive\": true,\n \"createdAt\": \"2024-01-02T11:15:00Z\",\n \"updatedAt\": \"2024-01-16T09:45:00Z\"\n }\n ],\n \"pagination\": {\n \"page\": 1,\n \"limit\": 20,\n \"total\": 1500,\n \"totalPages\": 75,\n \"hasNext\": true,\n \"hasPrev\": false\n }\n}" + } + ] + } + ] + }, + { + "name": "Orders", + "item": [ + { + "name": "Get User Orders", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/orders?page=1&limit=10&status=delivered", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "10" + }, + { + "key": "status", + "value": "delivered" + } + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/orders?page=1&limit=10", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "orders" + ], + "query": [ + { + "key": "page", + "value": "1" + }, + { + "key": "limit", + "value": "10" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"orders\": [\n {\n \"id\": \"order_001\",\n \"userId\": \"user_123\",\n \"items\": [\n {\n \"productId\": \"prod_001\",\n \"productName\": \"Wireless Bluetooth Headphones\",\n \"quantity\": 1,\n \"unitPrice\": 199.99,\n \"totalPrice\": 199.99\n }\n ],\n \"totalAmount\": 199.99,\n \"status\": \"delivered\",\n \"shippingAddress\": {\n \"street\": \"123 Main St\",\n \"city\": \"New York\",\n \"state\": \"NY\",\n \"zipCode\": \"10001\",\n \"country\": \"USA\"\n },\n \"paymentMethod\": \"credit_card\",\n \"createdAt\": \"2024-01-10T15:30:00Z\",\n \"updatedAt\": \"2024-01-12T10:15:00Z\"\n }\n ],\n \"pagination\": {\n \"page\": 1,\n \"limit\": 10,\n \"total\": 25,\n \"totalPages\": 3,\n \"hasNext\": true,\n \"hasPrev\": false\n }\n}" + } + ], + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "if (pm.response.json().orders.length > 0) {", + " pm.collectionVariables.set('orderId', pm.response.json().orders[0].id);", + "}" + ], + "type": "text/javascript" + } + } + ] + }, + { + "name": "Create Order", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"items\": [\n {\n \"productId\": \"prod_001\",\n \"quantity\": 2\n },\n {\n \"productId\": \"prod_002\",\n \"quantity\": 1\n }\n ],\n \"shippingAddress\": {\n \"street\": \"456 Oak Ave\",\n \"city\": \"Los Angeles\",\n \"state\": \"CA\",\n \"zipCode\": \"90210\",\n \"country\": \"USA\"\n },\n \"paymentMethod\": \"credit_card\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/orders", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "orders" + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"items\": [\n {\n \"productId\": \"prod_001\",\n \"quantity\": 2\n }\n ],\n \"shippingAddress\": {\n \"street\": \"456 Oak Ave\",\n \"city\": \"Los Angeles\",\n \"state\": \"CA\",\n \"zipCode\": \"90210\",\n \"country\": \"USA\"\n },\n \"paymentMethod\": \"credit_card\"\n}" + }, + "url": { + "raw": "{{baseUrl}}/orders", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "orders" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"id\": \"order_002\",\n \"userId\": \"user_123\",\n \"items\": [\n {\n \"productId\": \"prod_001\",\n \"productName\": \"Wireless Bluetooth Headphones\",\n \"quantity\": 2,\n \"unitPrice\": 199.99,\n \"totalPrice\": 399.98\n },\n {\n \"productId\": \"prod_002\",\n \"productName\": \"Smart Fitness Watch\",\n \"quantity\": 1,\n \"unitPrice\": 299.99,\n \"totalPrice\": 299.99\n }\n ],\n \"totalAmount\": 699.97,\n \"status\": \"pending\",\n \"shippingAddress\": {\n \"street\": \"456 Oak Ave\",\n \"city\": \"Los Angeles\",\n \"state\": \"CA\",\n \"zipCode\": \"90210\",\n \"country\": \"USA\"\n },\n \"paymentMethod\": \"credit_card\",\n \"createdAt\": \"2024-01-21T14:30:00Z\",\n \"updatedAt\": \"2024-01-21T14:30:00Z\"\n}" + } + ] + } + ] + }, + { + "name": "Payments", + "item": [ + { + "name": "Process Payment", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"orderId\": \"{{orderId}}\",\n \"amount\": 199.99,\n \"currency\": \"USD\",\n \"paymentMethod\": \"credit_card\",\n \"cardDetails\": {\n \"cardNumber\": \"4111111111111111\",\n \"expiryMonth\": 12,\n \"expiryYear\": 2025,\n \"cvv\": \"123\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + } + }, + "response": [ + { + "name": "Success Response", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"orderId\": \"order_001\",\n \"amount\": 199.99,\n \"currency\": \"USD\",\n \"paymentMethod\": \"credit_card\",\n \"cardDetails\": {\n \"cardNumber\": \"4111111111111111\",\n \"expiryMonth\": 12,\n \"expiryYear\": 2025,\n \"cvv\": \"123\"\n }\n}" + }, + "url": { + "raw": "{{baseUrl}}/payments", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "payments" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"transactionId\": \"txn_123456789\",\n \"status\": \"success\",\n \"amount\": 199.99,\n \"currency\": \"USD\",\n \"processedAt\": \"2024-01-21T15:45:30Z\"\n}" + } + ], + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Payment processed successfully', function () {", + " const response = pm.response.json();", + " pm.expect(response.status).to.eql('success');", + " pm.expect(response).to.have.property('transactionId');", + " pm.expect(response).to.have.property('amount');", + "});" + ], + "type": "text/javascript" + } + } + ] + } + ] + } + ] +} \ No newline at end of file