Exercise 7: Error Handling and Actions

This exercise shows simple error handling patterns that you can extend for your own applications with various HTTP status codes.

7.1 Basic Error Handling Examples

This exercise shows simple error handling patterns that you can extend for your own applications. We'll implement basic validation and error simulation in the mockserver.

Update webapp/localService/mainService/data/Books.js with basic error examples:

ex7/webapp/localService/mainService/data/Books.js View on GitHub ↗
case 'setDiscount':
  // Simple validation example - discount cannot exceed 90%
  if (actionData.percentage > 90) {
    this.throwError('Discount cannot exceed 90%', 400, {
      error: {
        code: 'INVALID_DISCOUNT',
        message: 'Company policy: Maximum discount is 90%',
      },
    });
  }

  // Simple 500 error simulation - uncomment to test
  // if (actionData.percentage === 42) {
  //   this.throwError('Simulated server error', 500);
  // }

  // Normal business logic continues...
  const currentEntries = await this.base.fetchEntries(keys);
  // ... rest of discount logic

7.2 Common Error Types

The mockserver supports standard HTTP error codes:

Status Code Error Type Use Case Example
400 Bad Request Input validation failures Invalid discount percentage
404 Not Found Entity doesn't exist Book ID not found
422 Unprocessable Entity Business rule violations Stock insufficient for operation
500 Internal Server Error System failures Database connection failed
501 Not Implemented Action not supported Unimplemented action name

7.3 Complete Error Implementation Example

Here's a more comprehensive example showing various error scenarios:

module.exports = {
  executeAction: async function (actionDefinition, actionData, keys, odataRequest) {
    console.log('Executing action:', actionDefinition.name);

    switch (actionDefinition.name) {
      case 'setDiscount':
        // Input validation - 400 Bad Request
        if (!actionData.percentage || actionData.percentage < 0) {
          this.throwError('Discount percentage is required and must be positive', 400, {
            error: {
              code: 'INVALID_INPUT',
              message: 'Percentage must be a positive number',
            },
          });
        }

        // Business rule validation - 400 Bad Request
        if (actionData.percentage > 90) {
          this.throwError('Discount cannot exceed 90%', 400, {
            error: {
              code: 'INVALID_DISCOUNT',
              message: 'Company policy: Maximum discount is 90%',
            },
          });
        }

        // Entity not found - 404 Not Found
        const currentEntries = await this.base.fetchEntries(keys);
        if (!currentEntries || currentEntries.length === 0) {
          this.throwError('Book not found', 404, {
            error: {
              code: 'ENTITY_NOT_FOUND',
              message: `Book with ID ${keys.ID} does not exist`,
            },
          });
        }

        // Business logic validation - 422 Unprocessable Entity
        const currentBook = currentEntries[0];
        if (currentBook.stock <= 0) {
          this.throwError('Cannot apply discount to out-of-stock items', 422, {
            error: {
              code: 'BUSINESS_RULE_VIOLATION',
              message: 'Discounts are not allowed for items with zero stock',
            },
          });
        }

        // Simulated system error - 500 Internal Server Error
        if (actionData.percentage === 42) {
          this.throwError('Simulated server error for testing', 500, {
            error: {
              code: 'INTERNAL_ERROR',
              message: 'Database connection temporarily unavailable',
            },
          });
        }

        // Normal business logic
        const discountMultiplier = (100 - actionData.percentage) / 100;
        const newPrice = Math.round(currentBook.price * discountMultiplier * 100) / 100;

        await this.base.updateEntry(keys, { price: newPrice });

        return {
          message: `${actionData.percentage}% discount applied successfully`,
          newPrice: newPrice,
          originalPrice: currentBook.price,
          discountAmount: currentBook.price - newPrice,
        };

      case 'promoteBook':
        // ... existing promote logic ...
        break;

      default:
        // 501 Not Implemented
        this.throwError(`Action ${actionDefinition.name} not implemented`, 501, {
          error: {
            code: 'NOT_IMPLEMENTED',
            message: `The action '${actionDefinition.name}' is not supported by this service`,
          },
        });
    }
  },
};

7.4 Testing Error Scenarios

Start the application:

# from folder ex7
npm run start-mock
# or from root folder
npm run start:ex7

Test different error conditions:

  1. Test 400 Bad Request: Try discount > 90% → Returns validation error with clear message
  2. Test 422 Business Rule: Apply discount to out-of-stock book → Returns business rule violation
  3. Test 500 Internal Error: Use percentage = 42 → Returns simulated server error
  4. Test 501 Not Implemented: Try unimplemented action → Returns "not supported" message
  5. Test 404 Not Found: Use invalid book ID → Returns "entity not found" error
UI Testing: These error scenarios help you test how your Fiori app handles different error conditions, improving the user experience with proper error messages and fallback behaviors.

7.5 Error Handling Best Practices

  • Meaningful Error Codes: Use consistent, descriptive error codes that frontend can handle appropriately
  • User-Friendly Messages: Provide clear, actionable error messages for end users
  • Structured Error Objects: Include additional context like field names, constraints, or suggestions
  • Appropriate HTTP Status: Use correct status codes that match the error scenario
  • Consistent Format: Maintain consistent error response structure across all actions
Result: Basic error handling patterns provide a foundation for comprehensive UI testing with realistic error scenarios.

Next Steps

Exercise 8 will show how to integrate the mockserver with wdi5 for offline end-to-end testing, enabling reliable CI/CD pipelines without backend dependencies.