diff --git a/README.md b/README.md index bee0721..cd7e450 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,9 @@ The `github-copilot-features-status` folder shows the main features of GitHub Co The `github-copilot-features` folder contains example files and data to demonstrate different functionalities and features: +- `advanced-refactor` - Advanced code refactoring scenarios for complex systems - `agent` - Example of using agent mode in github copilot +- `api-integration` - Examples of integrating with various APIs using GitHub Copilot - `change-format` - Example of converting data between different formats - `change-language` - Examples of converting code between different programming languages - `code-refactor` - Examples of GitHub API client implementations and code refactoring @@ -29,6 +31,7 @@ The `github-copilot-features` folder contains example files and data to demonstr - `notebook` - Examples of data analysis using Jupyter notebooks - `refactor` - Various code examples requiring refactoring - `reusable-prompts` - Examples of reusable prompts in github copilot +- `security-best-practices` - Security best practices implementation with GitHub Copilot - `translation` - translate document using github copilot - `unit-test` - Examples of unit tests using github copilot - `vision` - Examples of vision usage in github copilot @@ -39,7 +42,7 @@ The `github-copilot-features` folder contains example files and data to demonstr The `github-copilot-scenario-roles` folder contains use cases in different scenarios: - `common-scenario` - use github copilot in common scenario - `infrastructure-as-code` - use github copilot to compose infrastructure script -- `talk-to-database` - GitHub Copilot use cases in sql script generation with mcp +- `talk-to-database` - GitHub Copilot use cases in SQL script generation and database interaction patterns with mcp - `ui-automation` - GitHub Copilot use cases in UI automation script generation - `ui-design-dalle` - GitHub Copilot use cases in image generation automatically using dalle 3 - `ui-design-figma` - GitHub Copilot use cases in html generation with figma design diff --git a/github-copilot-features/advanced-refactor/README.md b/github-copilot-features/advanced-refactor/README.md new file mode 100644 index 0000000..ae228f3 --- /dev/null +++ b/github-copilot-features/advanced-refactor/README.md @@ -0,0 +1,64 @@ +# Advanced Refactoring with GitHub Copilot + +This directory contains examples of complex code that can benefit from advanced refactoring patterns with GitHub Copilot's assistance. + +## Lab Objectives + +- Learn how to identify and refactor code with multiple code smells and anti-patterns +- Use GitHub Copilot to suggest modern architecture patterns and best practices +- Transform legacy code into maintainable, testable, and scalable solutions + +## Exercises + +### 1. Legacy Service Refactoring + +The `legacy_service.js` file represents a typical legacy service with several issues: + +- Global state and side effects +- Callback-based asynchronous code +- Repetitive code blocks and tight coupling +- Lack of proper error handling +- No separation of concerns + +**Challenge:** Use GitHub Copilot to refactor this code to: +- Implement proper dependency injection +- Use modern async/await patterns +- Create appropriate class/module structure +- Add proper error handling +- Make the code more testable + +### 2. Complex Algorithm Optimization + +The `complex_algorithm.py` file contains an inefficient implementation of a data processing algorithm. + +**Challenge:** Use GitHub Copilot to: +- Optimize the algorithm for better performance +- Reduce memory usage +- Improve readability without sacrificing functionality +- Add appropriate comments explaining the optimization techniques + +### 3. Architectural Refactoring + +The `monolithic_app.js` represents a small monolithic application. + +**Challenge:** Use GitHub Copilot to: +- Refactor into a microservices or modular architecture +- Apply appropriate design patterns +- Implement proper separation of concerns +- Create interfaces between modules + +## Using GitHub Copilot for Advanced Refactoring + +1. Start by asking Copilot to analyze the existing code and identify issues +2. Ask for recommendations on modern architecture patterns that would fit +3. Break down the refactoring into manageable steps +4. Use inline chat to ask about specific sections of code +5. Have Copilot generate tests to verify the refactored code behaves correctly + +## Best Practices for Refactoring with Copilot + +- Always understand what the original code does before refactoring +- Break large refactoring tasks into smaller steps +- Verify each change with tests before moving to the next step +- Use Copilot to help identify edge cases you might have missed +- Ask Copilot to explain its refactoring decisions to better understand modern practices \ No newline at end of file diff --git a/github-copilot-features/advanced-refactor/complex_algorithm.py b/github-copilot-features/advanced-refactor/complex_algorithm.py new file mode 100644 index 0000000..73a7113 --- /dev/null +++ b/github-copilot-features/advanced-refactor/complex_algorithm.py @@ -0,0 +1,115 @@ +# An inefficient implementation of a data processing algorithm +# This code contains performance bottlenecks and inefficient data structures +# that can be optimized with GitHub Copilot's assistance + +def process_large_dataset(data, threshold=0.5): + """ + Process a large dataset with an inefficient algorithm. + This function has several performance issues: + - Redundant calculations + - Inefficient data structures + - Unnecessary copying of data + - Poor algorithm choice + + Parameters: + - data: List of dictionaries with 'id', 'value', and 'metadata' fields + - threshold: Threshold value for filtering + + Returns: + - Processed and filtered data + """ + # Initialize results + results = [] + + # Pre-process: extract values for later use + all_values = [] + for item in data: + all_values.append(item['value']) + + # Calculate statistics - inefficiently + total = 0 + for val in all_values: + total += val + mean = total / len(all_values) if len(all_values) > 0 else 0 + + # Calculate variance - inefficiently + variance = 0 + for val in all_values: + variance += (val - mean) ** 2 + variance = variance / len(all_values) if len(all_values) > 0 else 0 + + # Process each item - with redundant calculations + for item in data: + # Normalize value - redundantly calculated for each item + normalized_value = (item['value'] - mean) / (variance ** 0.5) if variance > 0 else 0 + + # Filter based on threshold + if normalized_value > threshold: + # Deep copy the item to avoid modifying original data + processed_item = {} + for key in item: + processed_item[key] = item[key] + + # Add derived fields + processed_item['normalized_value'] = normalized_value + processed_item['is_significant'] = normalized_value > 2 * threshold + + # Add to results + results.append(processed_item) + + # Sort results - inefficiently + for i in range(len(results)): + for j in range(i + 1, len(results)): + if results[i]['normalized_value'] < results[j]['normalized_value']: + results[i], results[j] = results[j], results[i] + + # Calculate additional metrics for filtered items - redundant loops + metadata_counts = {} + for item in results: + meta = item['metadata'] + if meta in metadata_counts: + metadata_counts[meta] = metadata_counts[meta] + 1 + else: + metadata_counts[meta] = 1 + + # Add frequency information to results - another loop through results + for item in results: + item['frequency'] = metadata_counts[item['metadata']] / len(results) if len(results) > 0 else 0 + + return results + + +def generate_test_data(size=1000): + """ + Generate test data for demonstration. + """ + import random + data = [] + for i in range(size): + data.append({ + 'id': i, + 'value': random.random() * 100, + 'metadata': random.choice(['A', 'B', 'C', 'D', 'E']) + }) + return data + + +# Example usage +if __name__ == "__main__": + # Generate sample data + test_data = generate_test_data(size=5000) + + # Time the processing + import time + start_time = time.time() + + # Process data + result = process_large_dataset(test_data) + + # Print execution time + print(f"Processed {len(test_data)} items in {time.time() - start_time:.4f} seconds") + print(f"Result contains {len(result)} items") + + # Print first few results + for i, item in enumerate(result[:5]): + print(f"{i+1}. ID: {item['id']}, Value: {item['value']:.2f}, Normalized: {item['normalized_value']:.2f}") \ No newline at end of file diff --git a/github-copilot-features/advanced-refactor/legacy_service.js b/github-copilot-features/advanced-refactor/legacy_service.js new file mode 100644 index 0000000..7eabd13 --- /dev/null +++ b/github-copilot-features/advanced-refactor/legacy_service.js @@ -0,0 +1,183 @@ +// This is an example of legacy service code that could benefit from advanced refactoring +// It contains multiple code smells and anti-patterns that GitHub Copilot can help identify and fix + +// Global variables and state +var users = []; +var orders = []; +var currentStatus = 'idle'; + +// Helper functions +function checkUserPermission(userId, action) { + var user = null; + for (var i = 0; i < users.length; i++) { + if (users[i].id === userId) { + user = users[i]; + break; + } + } + + if (user === null) { + return false; + } + + if (action === 'view') { + return user.role === 'admin' || user.role === 'viewer' || user.role === 'editor'; + } else if (action === 'edit') { + return user.role === 'admin' || user.role === 'editor'; + } else if (action === 'delete') { + return user.role === 'admin'; + } else { + return false; + } +} + +// Main service functions +function addUser(id, name, email, role) { + if (!id || !name || !email) { + console.error('Missing required fields'); + return false; + } + + for (var i = 0; i < users.length; i++) { + if (users[i].id === id) { + console.error('User already exists'); + return false; + } + } + + currentStatus = 'adding'; + + // Simulate API call delay + setTimeout(function() { + users.push({ + id: id, + name: name, + email: email, + role: role || 'viewer', + createdAt: new Date().toISOString() + }); + + currentStatus = 'idle'; + console.log('User added successfully'); + }, 100); + + return true; +} + +function getUser(id) { + for (var i = 0; i < users.length; i++) { + if (users[i].id === id) { + return users[i]; + } + } + return null; +} + +function deleteUser(userId, requestingUserId) { + if (!checkUserPermission(requestingUserId, 'delete')) { + console.error('Permission denied'); + return false; + } + + var userIndex = -1; + for (var i = 0; i < users.length; i++) { + if (users[i].id === userId) { + userIndex = i; + break; + } + } + + if (userIndex === -1) { + console.error('User not found'); + return false; + } + + // Check if user has orders + var hasOrders = false; + for (var i = 0; i < orders.length; i++) { + if (orders[i].userId === userId) { + hasOrders = true; + break; + } + } + + if (hasOrders) { + console.error('Cannot delete user with orders'); + return false; + } + + currentStatus = 'deleting'; + + // Simulate API call delay + setTimeout(function() { + users.splice(userIndex, 1); + currentStatus = 'idle'; + console.log('User deleted successfully'); + }, 100); + + return true; +} + +function createOrder(userId, items, quantity) { + var user = getUser(userId); + if (!user) { + console.error('User not found'); + return false; + } + + if (!items || !quantity) { + console.error('Missing order details'); + return false; + } + + currentStatus = 'ordering'; + + // Simulate API call delay + setTimeout(function() { + var order = { + id: 'order_' + Date.now(), + userId: userId, + items: items, + quantity: quantity, + status: 'created', + createdAt: new Date().toISOString() + }; + + orders.push(order); + currentStatus = 'idle'; + console.log('Order created successfully'); + }, 100); + + return true; +} + +function getOrdersByUser(userId, requestingUserId) { + if (!checkUserPermission(requestingUserId, 'view')) { + console.error('Permission denied'); + return null; + } + + var userOrders = []; + for (var i = 0; i < orders.length; i++) { + if (orders[i].userId === userId) { + userOrders.push(orders[i]); + } + } + + return userOrders; +} + +// Service status +function getStatus() { + return currentStatus; +} + +// Module exports (CommonJS style) +module.exports = { + addUser: addUser, + getUser: getUser, + deleteUser: deleteUser, + createOrder: createOrder, + getOrdersByUser: getOrdersByUser, + getStatus: getStatus +}; \ No newline at end of file diff --git a/github-copilot-features/advanced-refactor/monolithic_app.js b/github-copilot-features/advanced-refactor/monolithic_app.js new file mode 100644 index 0000000..74957b9 --- /dev/null +++ b/github-copilot-features/advanced-refactor/monolithic_app.js @@ -0,0 +1,528 @@ +// A monolithic application that handles user authentication, product management, +// order processing, and notification in a single file with tight coupling. + +const express = require('express'); +const bodyParser = require('body-parser'); +const bcrypt = require('bcrypt'); +const nodemailer = require('nodemailer'); +const fs = require('fs'); +const path = require('path'); + +// Initialize express app +const app = express(); +app.use(bodyParser.json()); + +// Database setup (in-memory for this example) +let db = { + users: [], + products: [], + orders: [], + sessions: {} +}; + +// Load data from JSON files if they exist +try { + if (fs.existsSync(path.join(__dirname, 'users.json'))) { + db.users = JSON.parse(fs.readFileSync(path.join(__dirname, 'users.json'))); + } + if (fs.existsSync(path.join(__dirname, 'products.json'))) { + db.products = JSON.parse(fs.readFileSync(path.join(__dirname, 'products.json'))); + } + if (fs.existsSync(path.join(__dirname, 'orders.json'))) { + db.orders = JSON.parse(fs.readFileSync(path.join(__dirname, 'orders.json'))); + } +} catch (err) { + console.error('Error loading data:', err); +} + +// Save data to JSON files +function saveData() { + try { + fs.writeFileSync(path.join(__dirname, 'users.json'), JSON.stringify(db.users, null, 2)); + fs.writeFileSync(path.join(__dirname, 'products.json'), JSON.stringify(db.products, null, 2)); + fs.writeFileSync(path.join(__dirname, 'orders.json'), JSON.stringify(db.orders, null, 2)); + } catch (err) { + console.error('Error saving data:', err); + } +} + +// Email setup +const transporter = nodemailer.createTransport({ + host: 'smtp.example.com', + port: 587, + secure: false, + auth: { + user: 'user@example.com', + pass: 'password123' + } +}); + +// Middleware for authentication +function authenticate(req, res, next) { + const sessionId = req.headers.authorization; + if (!sessionId || !db.sessions[sessionId]) { + return res.status(401).json({ error: 'Unauthorized' }); + } + req.user = db.sessions[sessionId]; + next(); +} + +// Admin middleware +function isAdmin(req, res, next) { + if (!req.user || req.user.role !== 'admin') { + return res.status(403).json({ error: 'Forbidden' }); + } + next(); +} + +// User Authentication Routes +app.post('/api/register', async (req, res) => { + try { + const { username, email, password } = req.body; + + // Validate input + if (!username || !email || !password) { + return res.status(400).json({ error: 'All fields are required' }); + } + + // Check if user already exists + if (db.users.some(u => u.email === email)) { + return res.status(400).json({ error: 'User already exists' }); + } + + // Hash password + const hashedPassword = await bcrypt.hash(password, 10); + + // Create user + const user = { + id: Date.now().toString(), + username, + email, + password: hashedPassword, + role: 'customer', + createdAt: new Date().toISOString() + }; + + db.users.push(user); + saveData(); + + // Send welcome email + const mailOptions = { + from: 'noreply@example.com', + to: email, + subject: 'Welcome to Our Store', + text: `Hi ${username}, thank you for registering!` + }; + + transporter.sendMail(mailOptions, (error) => { + if (error) { + console.error('Error sending email:', error); + } + }); + + res.status(201).json({ + message: 'User registered successfully', + id: user.id + }); + } catch (error) { + console.error('Registration error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.post('/api/login', async (req, res) => { + try { + const { email, password } = req.body; + + // Find user + const user = db.users.find(u => u.email === email); + if (!user) { + return res.status(400).json({ error: 'Invalid credentials' }); + } + + // Check password + const validPassword = await bcrypt.compare(password, user.password); + if (!validPassword) { + return res.status(400).json({ error: 'Invalid credentials' }); + } + + // Create session + const sessionId = Math.random().toString(36).substring(2, 15); + db.sessions[sessionId] = { + id: user.id, + email: user.email, + role: user.role + }; + + res.json({ + message: 'Login successful', + sessionId, + user: { + id: user.id, + username: user.username, + email: user.email, + role: user.role + } + }); + } catch (error) { + console.error('Login error:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.post('/api/logout', (req, res) => { + const sessionId = req.headers.authorization; + if (sessionId && db.sessions[sessionId]) { + delete db.sessions[sessionId]; + res.json({ message: 'Logout successful' }); + } else { + res.status(400).json({ error: 'Not logged in' }); + } +}); + +// Product Management Routes +app.get('/api/products', (req, res) => { + res.json(db.products); +}); + +app.get('/api/products/:id', (req, res) => { + const product = db.products.find(p => p.id === req.params.id); + if (product) { + res.json(product); + } else { + res.status(404).json({ error: 'Product not found' }); + } +}); + +app.post('/api/products', authenticate, isAdmin, (req, res) => { + try { + const { name, price, description, inventory } = req.body; + + if (!name || !price) { + return res.status(400).json({ error: 'Name and price are required' }); + } + + const product = { + id: Date.now().toString(), + name, + price: parseFloat(price), + description: description || '', + inventory: inventory || 0, + createdAt: new Date().toISOString(), + createdBy: req.user.id + }; + + db.products.push(product); + saveData(); + + res.status(201).json(product); + } catch (error) { + console.error('Error creating product:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.put('/api/products/:id', authenticate, isAdmin, (req, res) => { + try { + const { name, price, description, inventory } = req.body; + const productIndex = db.products.findIndex(p => p.id === req.params.id); + + if (productIndex === -1) { + return res.status(404).json({ error: 'Product not found' }); + } + + db.products[productIndex] = { + ...db.products[productIndex], + name: name || db.products[productIndex].name, + price: price ? parseFloat(price) : db.products[productIndex].price, + description: description !== undefined ? description : db.products[productIndex].description, + inventory: inventory !== undefined ? inventory : db.products[productIndex].inventory, + updatedAt: new Date().toISOString(), + updatedBy: req.user.id + }; + + saveData(); + + res.json(db.products[productIndex]); + } catch (error) { + console.error('Error updating product:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.delete('/api/products/:id', authenticate, isAdmin, (req, res) => { + try { + const productIndex = db.products.findIndex(p => p.id === req.params.id); + + if (productIndex === -1) { + return res.status(404).json({ error: 'Product not found' }); + } + + // Check if product is in any order + const productInOrders = db.orders.some(order => + order.items.some(item => item.productId === req.params.id) + ); + + if (productInOrders) { + return res.status(400).json({ error: 'Cannot delete product that is in orders' }); + } + + db.products.splice(productIndex, 1); + saveData(); + + res.json({ message: 'Product deleted successfully' }); + } catch (error) { + console.error('Error deleting product:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Order Processing Routes +app.get('/api/orders', authenticate, (req, res) => { + try { + let orders; + + if (req.user.role === 'admin') { + orders = db.orders; + } else { + orders = db.orders.filter(order => order.userId === req.user.id); + } + + res.json(orders); + } catch (error) { + console.error('Error fetching orders:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.post('/api/orders', authenticate, (req, res) => { + try { + const { items } = req.body; + + if (!items || !Array.isArray(items) || items.length === 0) { + return res.status(400).json({ error: 'Order must include items' }); + } + + // Validate items and calculate total + let total = 0; + const orderItems = []; + + for (const item of items) { + const product = db.products.find(p => p.id === item.productId); + + if (!product) { + return res.status(400).json({ error: `Product ${item.productId} not found` }); + } + + if (product.inventory < (item.quantity || 1)) { + return res.status(400).json({ error: `Product ${product.name} is out of stock` }); + } + + const quantity = item.quantity || 1; + const itemTotal = product.price * quantity; + + orderItems.push({ + productId: product.id, + productName: product.name, + price: product.price, + quantity, + total: itemTotal + }); + + total += itemTotal; + + // Update inventory + product.inventory -= quantity; + } + + // Create order + const order = { + id: Date.now().toString(), + userId: req.user.id, + items: orderItems, + total, + status: 'pending', + createdAt: new Date().toISOString() + }; + + db.orders.push(order); + saveData(); + + // Send order confirmation email + const user = db.users.find(u => u.id === req.user.id); + if (user) { + const mailOptions = { + from: 'orders@example.com', + to: user.email, + subject: `Order Confirmation #${order.id}`, + text: `Thank you for your order!\n\nOrder details:\nTotal: $${total.toFixed(2)}\n\nItems:\n${orderItems.map(i => + `${i.productName} - $${i.price} x ${i.quantity}`).join('\n')}` + }; + + transporter.sendMail(mailOptions, (error) => { + if (error) { + console.error('Error sending order email:', error); + } + }); + } + + res.status(201).json(order); + } catch (error) { + console.error('Error creating order:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.put('/api/orders/:id/status', authenticate, isAdmin, (req, res) => { + try { + const { status } = req.body; + const orderIndex = db.orders.findIndex(o => o.id === req.params.id); + + if (orderIndex === -1) { + return res.status(404).json({ error: 'Order not found' }); + } + + if (!['pending', 'processing', 'shipped', 'delivered', 'cancelled'].includes(status)) { + return res.status(400).json({ error: 'Invalid status' }); + } + + db.orders[orderIndex].status = status; + db.orders[orderIndex].updatedAt = new Date().toISOString(); + db.orders[orderIndex].updatedBy = req.user.id; + + saveData(); + + // Notify user of status change + const order = db.orders[orderIndex]; + const user = db.users.find(u => u.id === order.userId); + + if (user) { + const mailOptions = { + from: 'orders@example.com', + to: user.email, + subject: `Order #${order.id} Status Update`, + text: `Your order status has been updated to: ${status}` + }; + + transporter.sendMail(mailOptions, (error) => { + if (error) { + console.error('Error sending status update email:', error); + } + }); + } + + res.json({ + message: 'Order status updated', + order: db.orders[orderIndex] + }); + } catch (error) { + console.error('Error updating order status:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// User Management (Admin Only) +app.get('/api/users', authenticate, isAdmin, (req, res) => { + // Remove password from response + const users = db.users.map(user => { + const { password, ...userWithoutPassword } = user; + return userWithoutPassword; + }); + + res.json(users); +}); + +app.put('/api/users/:id/role', authenticate, isAdmin, (req, res) => { + try { + const { role } = req.body; + const userIndex = db.users.findIndex(u => u.id === req.params.id); + + if (userIndex === -1) { + return res.status(404).json({ error: 'User not found' }); + } + + if (!['admin', 'customer'].includes(role)) { + return res.status(400).json({ error: 'Invalid role' }); + } + + db.users[userIndex].role = role; + db.users[userIndex].updatedAt = new Date().toISOString(); + db.users[userIndex].updatedBy = req.user.id; + + // Update any active session for this user + Object.keys(db.sessions).forEach(sessionId => { + if (db.sessions[sessionId].id === req.params.id) { + db.sessions[sessionId].role = role; + } + }); + + saveData(); + + // Send notification email + const mailOptions = { + from: 'admin@example.com', + to: db.users[userIndex].email, + subject: 'Your Account Role Has Changed', + text: `Your account role has been updated to: ${role}` + }; + + transporter.sendMail(mailOptions, (error) => { + if (error) { + console.error('Error sending role update email:', error); + } + }); + + const { password, ...userWithoutPassword } = db.users[userIndex]; + res.json(userWithoutPassword); + } catch (error) { + console.error('Error updating user role:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Reports and Analytics (Admin Only) +app.get('/api/reports/sales', authenticate, isAdmin, (req, res) => { + try { + const { start, end } = req.query; + let startDate = start ? new Date(start) : new Date(0); + let endDate = end ? new Date(end) : new Date(); + + const filteredOrders = db.orders.filter(order => { + const orderDate = new Date(order.createdAt); + return orderDate >= startDate && orderDate <= endDate; + }); + + const totalSales = filteredOrders.reduce((sum, order) => sum + order.total, 0); + const salesByProduct = {}; + + filteredOrders.forEach(order => { + order.items.forEach(item => { + if (!salesByProduct[item.productId]) { + salesByProduct[item.productId] = { + productId: item.productId, + productName: item.productName, + quantity: 0, + revenue: 0 + }; + } + salesByProduct[item.productId].quantity += item.quantity; + salesByProduct[item.productId].revenue += item.total; + }); + }); + + res.json({ + totalSales, + orderCount: filteredOrders.length, + salesByProduct: Object.values(salesByProduct) + }); + } catch (error) { + console.error('Error generating sales report:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +// Start the server +const PORT = process.env.PORT || 3000; +app.listen(PORT, () => { + console.log(`Server running on port ${PORT}`); +}); \ No newline at end of file diff --git a/github-copilot-features/api-integration/README.md b/github-copilot-features/api-integration/README.md new file mode 100644 index 0000000..a38957e --- /dev/null +++ b/github-copilot-features/api-integration/README.md @@ -0,0 +1,75 @@ +# API Integration with GitHub Copilot + +This directory contains examples of API integration patterns using GitHub Copilot to help generate API client code, handle authentication, manage responses, and implement error handling strategies. + +## Lab Objectives + +- Learn how to use GitHub Copilot to write API client code for various services +- Generate proper authentication implementations for different API types +- Implement robust error handling and retry mechanisms +- Create effective data validation and transformation for API requests and responses +- Develop API integration tests + +## Exercises + +### 1. RESTful API Integration + +The files in the `rest` directory demonstrate integrating with various RESTful APIs: + +- `weather_api_client.js`: Integration with a weather API service +- `github_api.py`: Working with the GitHub API to manage repositories +- `payment_gateway.cs`: Integration with a payment processing API + +**Challenge:** Use GitHub Copilot to: +- Implement proper authentication +- Handle rate limiting +- Implement caching strategies +- Create robust error handling + +### 2. GraphQL API Integration + +The files in the `graphql` directory show how to work with GraphQL APIs: + +- `github_graphql.js`: Fetching repository and issue data from GitHub's GraphQL API + +**Challenge:** Use GitHub Copilot to: +- Generate complex GraphQL queries +- Implement paginated query handling +- Create proper type definitions for responses + +### 3. Authentication Patterns + +The files in the `auth` directory demonstrate various API authentication methods: + +- `oauth2_client.js`: OAuth 2.0 authentication flow implementation +- `api_key_rotation.py`: API key management with automatic rotation +- `jwt_authentication.js`: JWT token management for API access + +**Challenge:** Use GitHub Copilot to: +- Implement secure token storage +- Create token refresh logic +- Build proper error handling for authentication failures + +### 4. Advanced API Patterns + +Examples of more advanced API integration patterns: + +- `streaming_api_client.js`: Working with streaming APIs +- `webhook_handler.py`: Implementing webhook receivers with validation +- `api_versioning.js`: Handling API versioning in client code + +## Using GitHub Copilot for API Integration + +1. Start by asking Copilot to generate the basic API client structure +2. Request specific authentication implementation based on the API documentation +3. Have Copilot generate proper error handling and retry logic +4. Ask for help with data transformation between your application and the API +5. Generate comprehensive tests for your API integration + +## Best Practices for API Integration with Copilot + +- Always reference official API documentation when working with Copilot +- Remember to secure API keys and tokens (don't hardcode them) +- Ask Copilot to implement proper logging for API calls +- Use Copilot to generate comprehensive error handling for all API failure modes +- Have Copilot create unit tests that mock API responses \ No newline at end of file diff --git a/github-copilot-features/api-integration/auth/oauth2_client.js b/github-copilot-features/api-integration/auth/oauth2_client.js new file mode 100644 index 0000000..e598312 --- /dev/null +++ b/github-copilot-features/api-integration/auth/oauth2_client.js @@ -0,0 +1,183 @@ +// OAuth 2.0 Authentication Client +// This is a starting template for implementing OAuth 2.0 authentication +// Use GitHub Copilot to complete and enhance this implementation + +/** + * OAuth2Client - Handles OAuth 2.0 authentication flows + * Supports authorization code flow, client credentials, and refresh token + */ +class OAuth2Client { + /** + * Create an OAuth2 client + * + * @param {Object} config - Configuration object + * @param {string} config.clientId - OAuth client ID + * @param {string} config.clientSecret - OAuth client secret + * @param {string} config.redirectUri - Redirect URI for authorization code flow + * @param {string} config.authorizationEndpoint - Authorization endpoint + * @param {string} config.tokenEndpoint - Token endpoint + * @param {Array} config.scopes - OAuth scopes to request + */ + constructor(config) { + this.clientId = config.clientId; + this.clientSecret = config.clientSecret; + this.redirectUri = config.redirectUri; + this.authorizationEndpoint = config.authorizationEndpoint; + this.tokenEndpoint = config.tokenEndpoint; + this.scopes = config.scopes || []; + + this.accessToken = null; + this.refreshToken = null; + this.tokenExpiry = null; + } + + /** + * Generate the authorization URL for redirect + * + * @param {string} state - Random state parameter for CSRF protection + * @returns {string} - Authorization URL + */ + getAuthorizationUrl(state) { + // TODO: Implement this method to generate the authorization URL + // Include client_id, redirect_uri, response_type=code, scope, and state parameters + } + + /** + * Exchange authorization code for access token + * + * @param {string} code - Authorization code from redirect + * @returns {Promise} - Token response + */ + async getTokenFromCode(code) { + // TODO: Implement code exchange for token + // Make a POST request to the token endpoint with: + // - grant_type=authorization_code + // - code + // - redirect_uri + // - client_id + // - client_secret + // Store the received tokens and expiry time + } + + /** + * Get access token using client credentials grant + * + * @returns {Promise} - Token response + */ + async getTokenFromClientCredentials() { + // TODO: Implement client credentials flow + // Make a POST request to the token endpoint with: + // - grant_type=client_credentials + // - client_id + // - client_secret + // - scope (if required) + // Store the received tokens and expiry time + } + + /** + * Refresh the access token using a refresh token + * + * @returns {Promise} - New token response + */ + async refreshAccessToken() { + // TODO: Implement token refresh + // Check if we have a refresh token + // Make a POST request to the token endpoint with: + // - grant_type=refresh_token + // - refresh_token + // - client_id + // - client_secret + // Update stored tokens and expiry time + } + + /** + * Check if the current access token is expired + * + * @returns {boolean} - True if the token is expired or about to expire + */ + isTokenExpired() { + // TODO: Implement token expiry check + // Add a buffer time (e.g., 5 minutes) to handle request delays + } + + /** + * Get a valid access token, refreshing if necessary + * + * @returns {Promise} - Valid access token + */ + async getValidAccessToken() { + // TODO: Implement method to get a valid token + // Check if current token is expired and refresh if needed + // Return the access token + } + + /** + * Make an authenticated API request + * + * @param {string} url - API endpoint URL + * @param {Object} options - Fetch options + * @returns {Promise} - API response + */ + async makeAuthenticatedRequest(url, options = {}) { + // TODO: Implement authenticated request method + // 1. Get a valid access token + // 2. Add the token to the request headers + // 3. Make the request and handle response + // 4. If token is invalid, try to refresh and retry once + } + + /** + * Set token information manually (e.g., from storage) + * + * @param {Object} tokenInfo - Token information + * @param {string} tokenInfo.accessToken - Access token + * @param {string} tokenInfo.refreshToken - Refresh token + * @param {number} tokenInfo.expiresIn - Token lifetime in seconds + */ + setTokenInfo(tokenInfo) { + // TODO: Implement method to set token information + // Calculate and store the expiry time + } + + /** + * Get the current token information for storage + * + * @returns {Object} - Current token information + */ + getTokenInfo() { + // TODO: Return the current token information for storage + } +} + +// Example usage (uncomment to test): +/* +const client = new OAuth2Client({ + clientId: 'your-client-id', + clientSecret: 'your-client-secret', + redirectUri: 'http://localhost:3000/callback', + authorizationEndpoint: 'https://example.com/oauth/authorize', + tokenEndpoint: 'https://example.com/oauth/token', + scopes: ['read', 'write'] +}); + +// For authorization code flow: +// 1. Generate authorization URL +const authUrl = client.getAuthorizationUrl('random_state_123'); +console.log('Visit this URL to authorize:', authUrl); + +// 2. After redirect, exchange code for token +async function handleCallback(code) { + try { + const tokenResponse = await client.getTokenFromCode(code); + console.log('Authentication successful!', tokenResponse); + + // 3. Make authenticated requests + const apiResponse = await client.makeAuthenticatedRequest('https://api.example.com/data'); + console.log('API response:', apiResponse); + } catch (error) { + console.error('Authentication error:', error); + } +} +*/ + +module.exports = OAuth2Client; \ No newline at end of file diff --git a/github-copilot-features/api-integration/graphql/github_graphql.js b/github-copilot-features/api-integration/graphql/github_graphql.js new file mode 100644 index 0000000..8fc1cb4 --- /dev/null +++ b/github-copilot-features/api-integration/graphql/github_graphql.js @@ -0,0 +1,129 @@ +// GitHub GraphQL API Client +// This is a starting template for interacting with GitHub's GraphQL API +// Use GitHub Copilot to complete and enhance this implementation + +class GitHubGraphQLClient { + constructor(token, baseUrl = 'https://api.github.com/graphql') { + this.token = token; + this.baseUrl = baseUrl; + } + + /** + * Execute a GraphQL query against the GitHub API + * + * @param {string} query - The GraphQL query + * @param {Object} variables - Variables for the query + * @returns {Promise} - The query result + */ + async executeQuery(query, variables = {}) { + // TODO: Implement the method to execute a GraphQL query + // 1. Set up the request with proper headers (Authorization, Content-Type) + // 2. Send the query and variables as JSON in the request body + // 3. Parse and return the response data + // 4. Handle errors appropriately + } + + /** + * Fetch repository information including issues and pull requests + * + * @param {string} owner - Repository owner + * @param {string} name - Repository name + * @returns {Promise} - Repository data + */ + async getRepositoryInfo(owner, name) { + // TODO: Implement a method that fetches repository information + // Use a GraphQL query to get: + // - Repository name, description, and URL + // - Issue count and recent issues + // - Pull request count and recent PRs + // - Star count and language statistics + + // HINT: Use Copilot to help generate the GraphQL query + } + + /** + * Search for repositories matching a query + * + * @param {string} searchQuery - Search query + * @param {number} limit - Maximum number of results + * @returns {Promise} - Matching repositories + */ + async searchRepositories(searchQuery, limit = 10) { + // TODO: Implement repository search using GraphQL + // Get basic info about matching repositories + } + + /** + * Fetch a list of issues for a repository with pagination + * + * @param {string} owner - Repository owner + * @param {string} name - Repository name + * @param {string} state - Issue state (OPEN, CLOSED, ALL) + * @param {number} first - Number of issues to fetch + * @param {string|null} after - Pagination cursor + * @returns {Promise} - Issues with pagination info + */ + async getRepositoryIssues(owner, name, state = "OPEN", first = 10, after = null) { + // TODO: Implement paginated issue fetching + // Return both the issues and pagination information for the next page + } + + /** + * Get the authenticated user's information and repositories + * + * @returns {Promise} - User data and repositories + */ + async getViewerInfo() { + // TODO: Implement a method to fetch the current user's information + // Include repositories owned by the user + } + + /** + * Create a new issue on a repository + * + * @param {string} owner - Repository owner + * @param {string} name - Repository name + * @param {string} title - Issue title + * @param {string} body - Issue body + * @returns {Promise} - Created issue data + */ + async createIssue(owner, name, title, body) { + // TODO: Implement issue creation using GraphQL mutation + // Return the created issue's information + } +} + +// Example usage (uncomment to test): +/* +const client = new GitHubGraphQLClient('your-github-token'); + +async function testGitHubGraphQL() { + try { + // Get repository information + const repoInfo = await client.getRepositoryInfo('octocat', 'hello-world'); + console.log('Repository information:', repoInfo); + + // Search repositories + const searchResults = await client.searchRepositories('react language:javascript stars:>1000'); + console.log('Search results:', searchResults); + + // Get repository issues + const issues = await client.getRepositoryIssues('facebook', 'react', 'OPEN', 5); + console.log('Issues:', issues); + + // Get viewer info + const viewerInfo = await client.getViewerInfo(); + console.log('Viewer information:', viewerInfo); + + // Create an issue (only if you have permission) + // const newIssue = await client.createIssue('your-username', 'your-repo', 'Test issue', 'This is a test issue'); + // console.log('Created issue:', newIssue); + } catch (error) { + console.error('Error:', error.message); + } +} + +testGitHubGraphQL(); +*/ + +module.exports = GitHubGraphQLClient; \ No newline at end of file diff --git a/github-copilot-features/api-integration/rest/weather_api_client.js b/github-copilot-features/api-integration/rest/weather_api_client.js new file mode 100644 index 0000000..135a4ce --- /dev/null +++ b/github-copilot-features/api-integration/rest/weather_api_client.js @@ -0,0 +1,90 @@ +// Weather API Client +// This is a starting template for a weather API integration +// Use GitHub Copilot to complete and enhance this implementation + +class WeatherAPIClient { + constructor(apiKey, baseUrl = 'https://api.weatherapi.com/v1') { + this.apiKey = apiKey; + this.baseUrl = baseUrl; + } + + /** + * Get current weather for a location + * @param {string} location - City name, ZIP code, or coordinates + * @returns {Promise} - Weather data + */ + async getCurrentWeather(location) { + // TODO: Implement this method to fetch current weather data + // HINT: Use fetch or axios to make an API call to the /current.json endpoint + // Don't forget to handle errors and add the API key as a query parameter + } + + /** + * Get weather forecast for a location + * @param {string} location - City name, ZIP code, or coordinates + * @param {number} days - Number of days to forecast (1-10) + * @returns {Promise} - Forecast data + */ + async getForecast(location, days = 3) { + // TODO: Implement this method to fetch forecast data + // HINT: Use the /forecast.json endpoint with days parameter + } + + /** + * Search for locations matching a query + * @param {string} query - Search query + * @returns {Promise} - Matching locations + */ + async searchLocations(query) { + // TODO: Implement location search using the /search.json endpoint + } + + /** + * Get historical weather data + * @param {string} location - City name, ZIP code, or coordinates + * @param {string} date - Date in YYYY-MM-DD format + * @returns {Promise} - Historical weather data + */ + async getHistoricalWeather(location, date) { + // TODO: Implement this method to fetch historical weather data + // HINT: Use the /history.json endpoint + } + + /** + * Helper method to handle API errors + * @param {Response} response - Fetch response object + * @returns {Promise} - Parsed response data + * @throws {Error} - API error with status and message + */ + async handleResponse(response) { + // TODO: Implement error handling for API responses + // Parse JSON response, check for error codes, and throw appropriate errors + } +} + +// Example usage (uncomment to test): +/* +const weatherClient = new WeatherAPIClient('your-api-key-here'); + +async function testWeatherAPI() { + try { + const currentWeather = await weatherClient.getCurrentWeather('London'); + console.log('Current weather:', currentWeather); + + const forecast = await weatherClient.getForecast('New York', 5); + console.log('Forecast:', forecast); + + const locations = await weatherClient.searchLocations('San Fran'); + console.log('Locations:', locations); + + const history = await weatherClient.getHistoricalWeather('Tokyo', '2023-01-01'); + console.log('Historical weather:', history); + } catch (error) { + console.error('Error:', error.message); + } +} + +testWeatherAPI(); +*/ + +module.exports = WeatherAPIClient; \ No newline at end of file diff --git a/github-copilot-features/security-best-practices/README.md b/github-copilot-features/security-best-practices/README.md new file mode 100644 index 0000000..f09ec5b --- /dev/null +++ b/github-copilot-features/security-best-practices/README.md @@ -0,0 +1,81 @@ +# Security Best Practices with GitHub Copilot + +This directory contains examples of how GitHub Copilot can help developers implement security best practices and avoid common security pitfalls in their code. + +## Lab Objectives + +- Learn how to use GitHub Copilot to write secure code from the beginning +- Understand how to identify and fix common security vulnerabilities +- Implement proper authentication, authorization, and data validation +- Use Copilot to generate code that follows security best practices +- Learn how to use Copilot to audit existing code for security concerns + +## Exercises + +### 1. Secure Authentication + +The files in the `authentication` directory demonstrate secure authentication practices: + +- `password_validation.js`: Implementing strong password validation with appropriate complexity requirements +- `password_hashing.py`: Proper password hashing and verification +- `mfa_implementation.ts`: Multi-factor authentication implementation + +**Challenge:** Use GitHub Copilot to: +- Implement secure password policies following NIST guidelines +- Create proper password hashing with modern algorithms (bcrypt, Argon2) +- Implement 2FA/MFA with time-based one-time passwords (TOTP) + +### 2. Input Validation and Sanitization + +Examples in the `input-validation` directory show how to properly validate and sanitize user input: + +- `sql_injection_prevention.py`: Preventing SQL injection attacks +- `xss_prevention.js`: Defending against Cross-Site Scripting (XSS) +- `content_security_policy.js`: Implementing Content Security Policy headers + +**Challenge:** Use GitHub Copilot to: +- Implement parameterized queries to prevent SQL injection +- Create proper output encoding to prevent XSS +- Generate CSP headers that follow the principle of least privilege + +### 3. Secure API Design + +Examples in the `api-security` directory demonstrate secure API design principles: + +- `jwt_best_practices.js`: Secure JWT implementation and handling +- `api_rate_limiting.py`: Implementing rate limiting to prevent abuse +- `cors_configuration.js`: Properly configuring Cross-Origin Resource Sharing + +**Challenge:** Use GitHub Copilot to: +- Implement JWT with proper signing, expiration, and validation +- Create effective rate limiting with appropriate retry mechanisms +- Set up secure CORS policies that minimize exposure + +### 4. Secure Coding Patterns + +The `secure-coding` directory contains examples of secure coding patterns: + +- `secrets_management.js`: Properly handling secrets and credentials +- `secure_file_operations.py`: Safe file operations to prevent path traversal +- `secure_deserialization.java`: Preventing insecure deserialization vulnerabilities + +**Challenge:** Use GitHub Copilot to: +- Implement secure environment variable handling +- Create safe file upload and download functionality +- Build secure serialization/deserialization code + +## Using GitHub Copilot for Security + +1. Ask Copilot to explain security implications of code it generates +2. Request implementation of specific security controls +3. Have Copilot analyze existing code for security weaknesses +4. Generate test cases that include security edge cases +5. Ask for secure alternatives to potentially insecure practices + +## Best Practices for Security with Copilot + +- Always review security-critical code generated by Copilot +- Be explicit about security requirements when prompting +- Verify that generated code follows the latest security best practices +- Use additional security tools (SAST, DAST) to validate code +- Keep Copilot informed about specific security standards you need to follow (OWASP, NIST, etc.) \ No newline at end of file diff --git a/github-copilot-features/security-best-practices/api-security/jwt_best_practices.js b/github-copilot-features/security-best-practices/api-security/jwt_best_practices.js new file mode 100644 index 0000000..796c1f6 --- /dev/null +++ b/github-copilot-features/security-best-practices/api-security/jwt_best_practices.js @@ -0,0 +1,207 @@ +// JWT Best Practices for Secure API Authentication +// This file demonstrates secure JWT implementation patterns +// Use GitHub Copilot to improve and complete the implementation + +const crypto = require('crypto'); +const fs = require('fs'); +const path = require('path'); + +/** + * JWT Manager - Handles secure creation and validation of JWTs + * Demonstrates best practices for JWT usage in API authentication + */ +class JWTManager { + /** + * Initialize the JWT Manager + * + * @param {Object} options - Configuration options + * @param {string} options.algorithm - JWT signing algorithm (default: 'RS256') + * @param {number} options.expiresIn - Token expiration in seconds (default: 1 hour) + * @param {string} options.issuer - Token issuer identifier + * @param {string} options.audience - Token audience + */ + constructor(options = {}) { + this.algorithm = options.algorithm || 'RS256'; + this.expiresIn = options.expiresIn || 3600; // 1 hour default + this.issuer = options.issuer; + this.audience = options.audience; + + // For RS256, we need to load public and private keys + if (this.algorithm.startsWith('RS') || this.algorithm.startsWith('ES')) { + this.privateKey = null; + this.publicKey = null; + // Keys should be loaded securely + // TODO: Implement secure key loading + } else { + // For HMAC algorithms (HS256, HS384, HS512) + this.secretKey = null; + // TODO: Implement secure secret management + } + } + + /** + * Load RSA keys from files or environment + * + * @param {Object} options - Key options + * @param {string} options.privateKeyPath - Path to private key file + * @param {string} options.publicKeyPath - Path to public key file + */ + loadKeys(options = {}) { + // TODO: Implement secure key loading + // Consider environment variables for cloud environments + // Validate key formats and requirements + } + + /** + * Generate a random secure secret key for HMAC algorithms + * + * @param {number} bytes - Number of bytes for the key (default: 64) + * @returns {string} - Base64 encoded secret key + */ + generateSecretKey(bytes = 64) { + // TODO: Implement secure random key generation + } + + /** + * Create a JWT token for a user + * + * @param {Object} payload - Token payload with user information + * @param {string} payload.sub - Subject (user identifier) + * @param {Array} payload.roles - User roles for authorization + * @param {Object} additionalClaims - Additional claims to include + * @returns {string} - Signed JWT token + */ + createToken(payload, additionalClaims = {}) { + // TODO: Implement secure token creation + // 1. Verify required payload fields + // 2. Add standard claims (iat, exp, iss, aud, etc.) + // 3. Add protection against common attacks (jti, nonce) + // 4. Sign with appropriate key based on algorithm + } + + /** + * Verify and decode a JWT token + * + * @param {string} token - JWT token to verify + * @returns {Object} - Decoded token payload or null if invalid + */ + verifyToken(token) { + // TODO: Implement secure token verification + // 1. Verify signature using appropriate key + // 2. Validate standard claims (exp, nbf, iss, aud) + // 3. Check token type and other security requirements + } + + /** + * Parse a token without verifying signature (for debugging only) + * + * @param {string} token - JWT token to parse + * @returns {Object} - Decoded token parts + */ + parseToken(token) { + // TODO: Implement token parsing + // Warning: This does not validate the token! + try { + const parts = token.split('.'); + if (parts.length !== 3) { + return null; + } + + return { + header: JSON.parse(Buffer.from(parts[0], 'base64').toString()), + payload: JSON.parse(Buffer.from(parts[1], 'base64').toString()), + signature: parts[2] + }; + } catch (error) { + return null; + } + } + + /** + * Create a refresh token + * + * @param {string} userId - User identifier + * @param {string} tokenId - ID of the access token + * @returns {string} - Refresh token + */ + createRefreshToken(userId, tokenId) { + // TODO: Implement secure refresh token creation + // Consider using different signing key and longer expiration + } + + /** + * Rotate signing keys + * + * @returns {boolean} - True if keys were rotated successfully + */ + rotateKeys() { + // TODO: Implement key rotation + // Important for long-term security + } +} + +/** + * JWT Middleware Factory + * Creates Express middleware for JWT authentication + * + * @param {JWTManager} jwtManager - Configured JWT manager + * @param {Object} options - Middleware options + * @returns {Function} - Express middleware function + */ +function createJWTMiddleware(jwtManager, options = {}) { + // TODO: Implement JWT authentication middleware + // 1. Extract token from request (Authorization header, query param, etc.) + // 2. Verify the token + // 3. Handle errors appropriately + // 4. Add user information to request object +} + +// Example usage (uncomment to test): +/* +// Create JWT manager with appropriate options +const jwtManager = new JWTManager({ + algorithm: 'RS256', + expiresIn: 3600, + issuer: 'https://api.example.com', + audience: 'https://app.example.com' +}); + +// Load keys (in a real app, store these securely) +jwtManager.loadKeys({ + privateKeyPath: path.join(__dirname, 'private.pem'), + publicKeyPath: path.join(__dirname, 'public.pem') +}); + +// Create a token +const token = jwtManager.createToken({ + sub: 'user123', + roles: ['user'], + email: 'user@example.com' +}); + +console.log('Generated token:', token); + +// Verify the token +const decoded = jwtManager.verifyToken(token); +console.log('Verified token payload:', decoded); + +// Create Express middleware +const express = require('express'); +const app = express(); + +// Apply JWT authentication middleware +app.use('/api/protected', createJWTMiddleware(jwtManager)); + +// Protected route +app.get('/api/protected/data', (req, res) => { + res.json({ + message: 'Protected data', + user: req.user + }); +}); +*/ + +module.exports = { + JWTManager, + createJWTMiddleware +}; \ No newline at end of file diff --git a/github-copilot-features/security-best-practices/authentication/password_validation.js b/github-copilot-features/security-best-practices/authentication/password_validation.js new file mode 100644 index 0000000..75f5777 --- /dev/null +++ b/github-copilot-features/security-best-practices/authentication/password_validation.js @@ -0,0 +1,119 @@ +// Password Validation Module +// This file demonstrates secure password validation practices +// Use GitHub Copilot to implement proper validation following NIST guidelines + +/** + * Validates a password against security requirements + * + * Current implementation is incomplete and doesn't follow best practices. + * Use GitHub Copilot to improve this implementation and follow modern NIST guidelines. + * + * @param {string} password - The password to validate + * @returns {Object} - Validation result with isValid flag and reason if invalid + */ +function validatePassword(password) { + // TODO: Implement password validation following modern security guidelines + // Consider: + // 1. Minimum length requirement (NIST recommends at least 8 characters) + // 2. Check for common passwords or easily guessable patterns + // 3. Proper complexity requirements + // 4. Avoid arbitrary complexity rules that don't increase security + + // Basic implementation (needs improvement) + if (!password || typeof password !== 'string') { + return { + isValid: false, + reason: 'Password must be a non-empty string' + }; + } + + if (password.length < 8) { + return { + isValid: false, + reason: 'Password must be at least 8 characters long' + }; + } + + // This is an overly simplistic check that should be improved + const hasUpperCase = /[A-Z]/.test(password); + const hasLowerCase = /[a-z]/.test(password); + const hasDigit = /[0-9]/.test(password); + const hasSpecialChar = /[^A-Za-z0-9]/.test(password); + + if (!hasUpperCase || !hasLowerCase || !hasDigit || !hasSpecialChar) { + return { + isValid: false, + reason: 'Password must include uppercase, lowercase, number, and special character' + }; + } + + // TODO: Check against common password lists + // TODO: Implement better complexity validation + // TODO: Check for repeated patterns + + return { + isValid: true + }; +} + +/** + * Checks a password against a list of commonly used/breached passwords + * + * @param {string} password - The password to check + * @returns {boolean} - True if password is found in common password list + */ +function isCommonPassword(password) { + // TODO: Implement check against a list of common passwords + // Consider: + // 1. Using a proper password dictionary + // 2. Checking variations of the password + // 3. Implementing efficient lookup (hash-based) + + // This is just a placeholder - implement a real solution + const commonPasswords = [ + 'password', 'admin', '123456', 'qwerty', 'welcome', + 'password123', 'admin123', 'letmein', 'welcome1' + ]; + + return commonPasswords.includes(password.toLowerCase()); +} + +/** + * Estimates the strength of a password + * + * @param {string} password - The password to evaluate + * @returns {string} - Password strength rating (weak, medium, strong, very strong) + */ +function estimatePasswordStrength(password) { + // TODO: Implement password strength estimation + // Consider entropy, length, character distribution, etc. +} + +/** + * Generates secure password requirements messaging for users + * + * @returns {string} - User-friendly password requirements message + */ +function getPasswordRequirementsMessage() { + // TODO: Generate user-friendly password requirements message + // Focus on encouraging passphrases and length rather than complexity rules +} + +/** + * Suggests improvements for a weak password + * + * @param {string} password - The weak password + * @returns {Array} - List of suggested improvements + */ +function suggestPasswordImprovements(password) { + // TODO: Suggest specific improvements for the password + // Don't include the original password in suggestions +} + +module.exports = { + validatePassword, + isCommonPassword, + estimatePasswordStrength, + getPasswordRequirementsMessage, + suggestPasswordImprovements +}; \ No newline at end of file diff --git a/github-copilot-features/security-best-practices/input-validation/sql_injection_prevention.py b/github-copilot-features/security-best-practices/input-validation/sql_injection_prevention.py new file mode 100644 index 0000000..3fd7df7 --- /dev/null +++ b/github-copilot-features/security-best-practices/input-validation/sql_injection_prevention.py @@ -0,0 +1,257 @@ +# SQL Injection Prevention Example +# This file demonstrates how to prevent SQL injection attacks +# Use GitHub Copilot to implement proper database access patterns + +import sqlite3 +from typing import List, Dict, Any, Optional, Union + + +class DatabaseManager: + """ + Database manager with examples of vulnerable and secure database operations. + This class demonstrates how to prevent SQL injection vulnerabilities. + + Use GitHub Copilot to: + 1. Identify vulnerable code patterns + 2. Fix the vulnerable functions + 3. Add proper input validation and parameterization + """ + + def __init__(self, db_path: str): + """Initialize the database connection""" + self.db_path = db_path + self.connection = None + + def connect(self) -> None: + """Establish database connection""" + self.connection = sqlite3.connect(self.db_path) + self.connection.row_factory = sqlite3.Row + + def close(self) -> None: + """Close database connection if open""" + if self.connection: + self.connection.close() + self.connection = None + + # VULNERABLE FUNCTION - DO NOT USE + def get_user_vulnerable(self, username: str) -> Dict[str, Any]: + """ + VULNERABLE TO SQL INJECTION - DO NOT USE + Demonstrates vulnerable SQL query construction + + Args: + username: Username to search for + + Returns: + User data as dictionary + """ + if not self.connection: + self.connect() + + cursor = self.connection.cursor() + + # VULNERABLE: Direct string concatenation in SQL query + query = f"SELECT id, username, email, role FROM users WHERE username = '{username}'" + cursor.execute(query) + + user = cursor.fetchone() + if user: + return dict(user) + return {} + + # TODO: Implement the secure version of get_user + def get_user_secure(self, username: str) -> Dict[str, Any]: + """ + Secure version of user retrieval + Uses parameterized queries to prevent SQL injection + + Args: + username: Username to search for + + Returns: + User data as dictionary + """ + # TODO: Implement secure parameterized query + pass + + # VULNERABLE FUNCTION - DO NOT USE + def search_products_vulnerable(self, search_term: str) -> List[Dict[str, Any]]: + """ + VULNERABLE TO SQL INJECTION - DO NOT USE + Demonstrates vulnerable SQL search implementation + + Args: + search_term: Term to search for in products + + Returns: + List of matching products + """ + if not self.connection: + self.connect() + + cursor = self.connection.cursor() + + # VULNERABLE: Direct string concatenation with minimal escaping + search_term = search_term.replace("'", "''") # Inadequate protection! + query = f"SELECT * FROM products WHERE name LIKE '%{search_term}%' OR description LIKE '%{search_term}%'" + cursor.execute(query) + + results = cursor.fetchall() + return [dict(row) for row in results] + + # TODO: Implement the secure version of search_products + def search_products_secure(self, search_term: str) -> List[Dict[str, Any]]: + """ + Secure version of product search + Uses parameterized queries to prevent SQL injection + + Args: + search_term: Term to search for in products + + Returns: + List of matching products + """ + # TODO: Implement secure parameterized search + pass + + # VULNERABLE FUNCTION - DO NOT USE + def update_user_profile_vulnerable(self, user_id: int, data: Dict[str, Any]) -> bool: + """ + VULNERABLE TO SQL INJECTION - DO NOT USE + Demonstrates vulnerable dynamic SQL query building + + Args: + user_id: ID of user to update + data: Dictionary of fields to update + + Returns: + True if update successful + """ + if not self.connection: + self.connect() + + cursor = self.connection.cursor() + + # VULNERABLE: Constructing query from user-provided dictionary keys and values + set_clauses = [] + for key, value in data.items(): + # Inadequate escaping and validation + escaped_value = str(value).replace("'", "''") + set_clauses.append(f"{key} = '{escaped_value}'") + + set_clause = ", ".join(set_clauses) + query = f"UPDATE users SET {set_clause} WHERE id = {user_id}" + + cursor.execute(query) + self.connection.commit() + return cursor.rowcount > 0 + + # TODO: Implement the secure version of update_user_profile + def update_user_profile_secure(self, user_id: int, data: Dict[str, Any]) -> bool: + """ + Secure version of user profile update + Uses parameterized queries and validates field names + + Args: + user_id: ID of user to update + data: Dictionary of fields to update + + Returns: + True if update successful + """ + # TODO: Implement secure parameterized update + # 1. Validate field names against whitelist + # 2. Use parameterized queries for values + # 3. Handle data types appropriately + pass + + # Additional functions to implement: + # TODO: Implement secure batch operation function + # TODO: Implement function to safely handle dynamic sorting + # TODO: Implement function to safely execute raw queries for admin purposes + + +# Example setup code +def create_test_database(db_path: str) -> None: + """Create a test database with sample tables""" + connection = sqlite3.connect(db_path) + cursor = connection.cursor() + + # Create users table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + username TEXT UNIQUE NOT NULL, + email TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + role TEXT NOT NULL + ) + ''') + + # Create products table + cursor.execute(''' + CREATE TABLE IF NOT EXISTS products ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + description TEXT, + price REAL NOT NULL, + category TEXT + ) + ''') + + # Add sample data + cursor.execute(''' + INSERT OR IGNORE INTO users (username, email, password, role) + VALUES + ('admin', 'admin@example.com', 'hashed_password_here', 'admin'), + ('user1', 'user1@example.com', 'hashed_password_here', 'user') + ''') + + cursor.execute(''' + INSERT OR IGNORE INTO products (name, description, price, category) + VALUES + ('Laptop', 'High performance laptop', 999.99, 'Electronics'), + ('Smartphone', 'Latest model', 699.99, 'Electronics'), + ('Headphones', 'Noise cancelling', 199.99, 'Accessories') + ''') + + connection.commit() + connection.close() + + +# Example usage (uncomment to test) +""" +if __name__ == "__main__": + import os + + # Create test database + db_path = "test_database.db" + if not os.path.exists(db_path): + create_test_database(db_path) + + # Initialize manager + db_manager = DatabaseManager(db_path) + + # Example of secure vs vulnerable + print("SECURE QUERY:") + user = db_manager.get_user_secure("admin") + print(user) + + print("\nSECURE SEARCH:") + products = db_manager.search_products_secure("laptop") + print(products) + + # DO NOT run vulnerable examples in production! + # These are for demonstration purposes only + print("\nVULNERABLE QUERY - DON'T USE:") + # This should be safe: + user = db_manager.get_user_vulnerable("admin") + print(user) + + # But this would be vulnerable: + # malicious = "admin' OR '1'='1" + # user = db_manager.get_user_vulnerable(malicious) + # print(user) + + db_manager.close() +""" \ No newline at end of file diff --git a/github-copilot-features/unit-test/complex_mocking.test.js b/github-copilot-features/unit-test/complex_mocking.test.js new file mode 100644 index 0000000..3ab9500 --- /dev/null +++ b/github-copilot-features/unit-test/complex_mocking.test.js @@ -0,0 +1,289 @@ +// Complex Mocking and Unit Testing Examples +// This file demonstrates advanced unit testing techniques with complex mocking + +// Let's imagine we're testing a service that: +// 1. Fetches data from multiple APIs +// 2. Processes the data and combines results +// 3. Interacts with a database +// 4. Has complex dependencies and side effects + +// First, define the modules we'll need to mock +jest.mock('axios'); +jest.mock('../database'); // Assuming there's a database module +jest.mock('../cache'); // Assuming there's a caching module +jest.mock('../eventBus'); // Assuming there's an event bus module + +// Import the mocked modules +const axios = require('axios'); +const db = require('../database'); +const cache = require('../cache'); +const eventBus = require('../eventBus'); + +// Import the service we're testing +const dataService = require('../services/dataService'); + +describe('Data Service - Complex Unit Tests', () => { + // Setup before each test + beforeEach(() => { + // Clear all mocks before each test + jest.clearAllMocks(); + + // Set up the cache mock to return specific values + cache.get.mockImplementation((key) => { + // Return null to simulate cache miss and force API calls + return null; + }); + + // Set up the database mock with test data + db.getUserPreferences.mockResolvedValue({ + userId: 'user123', + preferences: { + dataSource: 'api1', + refreshInterval: 60, + filters: ['important', 'urgent'] + } + }); + + // Set up event bus mock to track published events + eventBus.publish.mockImplementation(() => Promise.resolve()); + }); + + // Test case: Happy path - all APIs respond successfully + test('should fetch, process, and combine data from multiple sources', async () => { + // Arrange: Mock successful API responses + axios.get.mockImplementation((url) => { + if (url.includes('/api1/data')) { + return Promise.resolve({ + data: { + items: [ + { id: 1, name: 'Item 1', category: 'important', value: 100 }, + { id: 2, name: 'Item 2', category: 'normal', value: 50 } + ], + meta: { total: 2 } + } + }); + } else if (url.includes('/api2/records')) { + return Promise.resolve({ + data: { + records: [ + { recordId: 'a1', title: 'Record A', priority: 'urgent', amount: 75 }, + { recordId: 'b2', title: 'Record B', priority: 'low', amount: 25 } + ], + pagination: { total: 2, page: 1, pageSize: 10 } + } + }); + } + // Return empty data for any other endpoints + return Promise.resolve({ data: {} }); + }); + + // Mock the database save operation + db.saveProcessedData.mockResolvedValue({ success: true, id: 'process123' }); + + // Act: Call the service method + const result = await dataService.fetchAndProcessData('user123'); + + // Assert: Verify the expected results + // Verify API calls were made + expect(axios.get).toHaveBeenCalledTimes(2); + expect(axios.get).toHaveBeenCalledWith( + 'https://example.com/api1/data', + expect.objectContaining({ + headers: expect.objectContaining({ + 'Authorization': expect.any(String) + }) + }) + ); + + // Verify database interactions + expect(db.getUserPreferences).toHaveBeenCalledWith('user123'); + expect(db.saveProcessedData).toHaveBeenCalledWith( + 'user123', + expect.objectContaining({ + items: expect.any(Array), + timestamp: expect.any(Number) + }) + ); + + // Verify cache interactions + expect(cache.set).toHaveBeenCalledWith( + expect.stringContaining('user123'), + expect.any(Object), + 60 // Should use the user's preference for cache time + ); + + // Verify event bus was called + expect(eventBus.publish).toHaveBeenCalledWith( + 'dataProcessed', + expect.objectContaining({ + userId: 'user123', + itemCount: expect.any(Number) + }) + ); + + // Verify the result structure + expect(result).toEqual({ + success: true, + data: { + combinedItems: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + name: expect.any(String), + category: expect.any(String), + value: expect.any(Number) + }) + ]), + stats: { + total: expect.any(Number), + byCategory: expect.any(Object), + averageValue: expect.any(Number) + }, + filters: expect.arrayContaining(['important', 'urgent']), + timestamp: expect.any(Number) + } + }); + + // Verify data transformation logic + const { combinedItems } = result.data; + expect(combinedItems.length).toBeGreaterThanOrEqual(2); + + // Filter items and check processed data + const importantItems = combinedItems.filter(item => + item.category === 'important' || item.category === 'urgent'); + expect(importantItems.length).toBeGreaterThan(0); + }); + + // Test case: API failure handling + test('should handle API failures gracefully', async () => { + // Arrange: Mock API failure for one endpoint + axios.get.mockImplementation((url) => { + if (url.includes('/api1/data')) { + return Promise.resolve({ + data: { + items: [ + { id: 1, name: 'Item 1', category: 'important', value: 100 } + ], + meta: { total: 1 } + } + }); + } else if (url.includes('/api2/records')) { + // Simulate API failure + return Promise.reject(new Error('API unavailable')); + } + return Promise.resolve({ data: {} }); + }); + + // Act: Call the service method + const result = await dataService.fetchAndProcessData('user123'); + + // Assert: Verify error handling + // Service should still return data from successful API call + expect(result).toEqual({ + success: true, + data: expect.objectContaining({ + combinedItems: expect.any(Array), + partial: true, // Should indicate partial data + errors: expect.arrayContaining([ + expect.objectContaining({ + source: 'api2', + message: expect.stringContaining('API unavailable') + }) + ]) + }) + }); + + // Verify error was logged + expect(eventBus.publish).toHaveBeenCalledWith( + 'error', + expect.objectContaining({ + message: expect.stringContaining('API unavailable'), + source: 'dataService' + }) + ); + + // Verify we still processed what we could + expect(db.saveProcessedData).toHaveBeenCalled(); + }); + + // Test case: Cache hit scenario + test('should use cached data when available', async () => { + // Arrange: Mock cache hit + const cachedData = { + combinedItems: [ + { id: 'cached1', name: 'Cached Item 1', category: 'important', value: 100 } + ], + stats: { + total: 1, + byCategory: { important: 1 }, + averageValue: 100 + }, + timestamp: Date.now() - 30000 // 30 seconds ago + }; + + cache.get.mockImplementation((key) => { + if (key.includes('user123')) { + return cachedData; + } + return null; + }); + + // Act: Call the service method + const result = await dataService.fetchAndProcessData('user123'); + + // Assert: Verify cache was used + expect(axios.get).not.toHaveBeenCalled(); // No API calls + expect(db.saveProcessedData).not.toHaveBeenCalled(); // No DB save + + // Verify cache hit was logged + expect(eventBus.publish).toHaveBeenCalledWith( + 'cacheHit', + expect.objectContaining({ + userId: 'user123', + cacheKey: expect.stringContaining('user123') + }) + ); + + // Verify result matches cached data + expect(result).toEqual({ + success: true, + data: expect.objectContaining({ + combinedItems: cachedData.combinedItems, + stats: cachedData.stats, + fromCache: true, + timestamp: cachedData.timestamp + }) + }); + }); + + // Add more test scenarios here: + // - Test with different user preferences + // - Test pagination and combining multiple pages + // - Test retry mechanisms + // - Test concurrency handling +}); + +// Additional test suite for other service functions +describe('Data Service - Data Transformation Tests', () => { + test('should transform data according to user preferences', () => { + // Test data transformation logic independently + }); + + // More transformation tests... +}); + +// Test suite for error scenarios +describe('Data Service - Error Handling Tests', () => { + test('should handle malformed API responses', async () => { + // Test with malformed data from APIs + }); + + test('should handle database connectivity issues', async () => { + // Test database failure scenarios + }); + + // More error handling tests... +}); + +// Note: This file is a template - some imports and implementation details +// are omitted as they would be specific to your codebase +// GitHub Copilot can help you adapt this to your specific testing needs \ No newline at end of file diff --git a/github-copilot-features/unit-test/integration_testing.test.js b/github-copilot-features/unit-test/integration_testing.test.js new file mode 100644 index 0000000..f206f70 --- /dev/null +++ b/github-copilot-features/unit-test/integration_testing.test.js @@ -0,0 +1,308 @@ +// Integration Testing Example with Complex Dependencies +// This file demonstrates how to write integration tests for systems with multiple components + +// Mocking approach: partial mocks to test integration while isolating external dependencies +const mockExternalAPI = { + fetchData: jest.fn(), + submitData: jest.fn() +}; + +// Mock external services but use real database connection for integration +jest.mock('../services/externalAPI', () => mockExternalAPI); +jest.mock('../services/metrics', () => ({ + recordMetric: jest.fn(), + getMetrics: jest.fn() +})); + +// Import real implementations for core components we want to test +const { OrderService } = require('../services/orderService'); +const { PaymentProcessor } = require('../services/paymentProcessor'); +const { InventoryManager } = require('../services/inventoryManager'); +const { NotificationService } = require('../services/notificationService'); +const { DatabaseConnection } = require('../database/connection'); +const metrics = require('../services/metrics'); + +// Create test database connection +let dbConnection; +let orderService; +let paymentProcessor; +let inventoryManager; +let notificationService; + +describe('Order Processing Integration Tests', () => { + // Setup before all tests - connect to test database + beforeAll(async () => { + // Connect to test database (could be in-memory or dedicated test instance) + dbConnection = new DatabaseConnection({ + host: 'localhost', + database: 'test_db', + user: 'test_user', + password: 'test_password', + // Flag to use mock or in-memory implementation for tests + mockForTesting: true + }); + + await dbConnection.connect(); + + // Initialize services with the test database connection + paymentProcessor = new PaymentProcessor(dbConnection); + inventoryManager = new InventoryManager(dbConnection); + notificationService = new NotificationService(dbConnection); + + // OrderService depends on all other services + orderService = new OrderService({ + dbConnection, + paymentProcessor, + inventoryManager, + notificationService + }); + }); + + // Clean up after all tests + afterAll(async () => { + await dbConnection.disconnect(); + }); + + // Setup before each test + beforeEach(async () => { + // Reset all mocks + jest.clearAllMocks(); + + // Clean database tables + await dbConnection.query('DELETE FROM orders'); + await dbConnection.query('DELETE FROM inventory_transactions'); + await dbConnection.query('DELETE FROM payments'); + await dbConnection.query('DELETE FROM notifications'); + + // Seed database with test data + await dbConnection.query(` + INSERT INTO products (id, name, price, inventory_count) + VALUES + (1, 'Test Product 1', 10.99, 100), + (2, 'Test Product 2', 25.50, 50), + (3, 'Test Product 3', 5.99, 0) + `); + + await dbConnection.query(` + INSERT INTO customers (id, name, email) + VALUES (1, 'Test Customer', 'test@example.com') + `); + + // Set up mock external API responses + mockExternalAPI.fetchData.mockImplementation(async (endpoint) => { + if (endpoint === 'shipping_rates') { + return { + rates: [ + { service: 'standard', price: 5.99 }, + { service: 'express', price: 15.99 } + ] + }; + } else if (endpoint === 'tax_rates') { + return { + rate: 0.08 // 8% tax rate + }; + } + return {}; + }); + + mockExternalAPI.submitData.mockResolvedValue({ + success: true, + transactionId: 'mock-transaction-123' + }); + }); + + // Integration test: Complete order processing flow + test('should process a complete order with payment, inventory and notification', async () => { + // Arrange + const orderData = { + customerId: 1, + items: [ + { productId: 1, quantity: 2 }, + { productId: 2, quantity: 1 } + ], + shippingAddress: { + street: '123 Test St', + city: 'Test City', + state: 'TS', + zip: '12345' + }, + shippingMethod: 'standard', + paymentDetails: { + type: 'credit_card', + cardNumber: '4111111111111111', + expiryMonth: '12', + expiryYear: '2030', + cvv: '123' + } + }; + + // Act + const result = await orderService.createOrder(orderData); + + // Assert + expect(result).toEqual(expect.objectContaining({ + success: true, + orderId: expect.any(String), + total: expect.any(Number), + status: 'confirmed' + })); + + // Verify that all services were called appropriately + + // 1. Verify inventory was checked and updated + const inventoryCheck = await dbConnection.query( + 'SELECT * FROM inventory_transactions WHERE order_id = ?', + [result.orderId] + ); + expect(inventoryCheck.length).toBe(2); // Two products reserved + + // 2. Verify payment was processed + const paymentRecord = await dbConnection.query( + 'SELECT * FROM payments WHERE order_id = ?', + [result.orderId] + ); + expect(paymentRecord.length).toBe(1); + expect(paymentRecord[0]).toEqual(expect.objectContaining({ + status: 'completed', + amount: result.total + })); + + // 3. Verify notification was sent + const notificationRecord = await dbConnection.query( + 'SELECT * FROM notifications WHERE reference_id = ?', + [result.orderId] + ); + expect(notificationRecord.length).toBeGreaterThan(0); + expect(notificationRecord).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'order_confirmation', + recipient: 'test@example.com' + }) + ]) + ); + + // 4. Verify external API was called for shipping rates + expect(mockExternalAPI.fetchData).toHaveBeenCalledWith('shipping_rates'); + + // 5. Verify metrics were recorded + expect(metrics.recordMetric).toHaveBeenCalledWith( + 'order_created', + expect.any(Object) + ); + }); + + // Integration test: Order with out-of-stock item + test('should fail to create order with out-of-stock items', async () => { + // Arrange + const orderData = { + customerId: 1, + items: [ + { productId: 3, quantity: 1 } // This product has 0 inventory + ], + shippingAddress: { + street: '123 Test St', + city: 'Test City', + state: 'TS', + zip: '12345' + }, + shippingMethod: 'standard', + paymentDetails: { + type: 'credit_card', + cardNumber: '4111111111111111', + expiryMonth: '12', + expiryYear: '2030', + cvv: '123' + } + }; + + // Act + const result = await orderService.createOrder(orderData); + + // Assert + expect(result).toEqual(expect.objectContaining({ + success: false, + error: expect.stringContaining('out of stock'), + code: 'INVENTORY_ERROR' + })); + + // Verify no payment was processed + const paymentRecord = await dbConnection.query( + 'SELECT * FROM payments WHERE order_id = ?', + [result.orderId] + ); + expect(paymentRecord.length).toBe(0); + + // Verify error metric was recorded + expect(metrics.recordMetric).toHaveBeenCalledWith( + 'order_error', + expect.objectContaining({ + errorCode: 'INVENTORY_ERROR' + }) + ); + }); + + // Integration test: Payment processing error + test('should handle payment processing errors', async () => { + // Arrange - Set up payment processor to simulate failure + // We're using a spy to modify one method while keeping others intact + const processPaymentSpy = jest + .spyOn(paymentProcessor, 'processPayment') + .mockImplementation(async () => { + throw new Error('Payment declined: Insufficient funds'); + }); + + const orderData = { + customerId: 1, + items: [ + { productId: 1, quantity: 1 } + ], + shippingAddress: { + street: '123 Test St', + city: 'Test City', + state: 'TS', + zip: '12345' + }, + shippingMethod: 'standard', + paymentDetails: { + type: 'credit_card', + cardNumber: '4111111111111111', + expiryMonth: '12', + expiryYear: '2030', + cvv: '123' + } + }; + + // Act + const result = await orderService.createOrder(orderData); + + // Assert + expect(result).toEqual(expect.objectContaining({ + success: false, + error: expect.stringContaining('Payment declined'), + code: 'PAYMENT_ERROR' + })); + + // Verify inventory was rolled back + const inventoryCheck = await dbConnection.query( + 'SELECT * FROM inventory_transactions WHERE order_id = ? AND type = ?', + [result.orderId, 'reservation_rollback'] + ); + expect(inventoryCheck.length).toBeGreaterThan(0); + + // Clean up spy + processPaymentSpy.mockRestore(); + }); + + // More integration tests... +}); + +// Testing transaction handling and rollbacks +describe('Transaction Handling Integration Tests', () => { + // Test transaction isolation and rollbacks + // ... +}); + +// Note: This file is a template - some imports and implementation details +// are omitted as they would be specific to your codebase +// GitHub Copilot can help you adapt this to your specific testing needs \ No newline at end of file diff --git a/github-copilot-lab/note.md b/github-copilot-lab/note.md index 6f2f692..4a4a910 100644 --- a/github-copilot-lab/note.md +++ b/github-copilot-lab/note.md @@ -6,6 +6,7 @@ - `translation` - translate document using github copilot - `explain` - Examples of code explanations - `refactor` - Various code examples requiring refactoring +- `advanced-refactor` - Advanced code refactoring scenarios for complex systems - `code-review` - Example files for code review demonstrations - `unit-test` - Examples of unit tests using github copilot - `custom-instruction` - use custom instructions to customize the behavior in github copilot @@ -18,11 +19,13 @@ - `reusable-prompts` - Examples of reusable prompts in github copilot - `common-scenario` - use github copilot in common scenario - `nes` - next edit suggestion in github copilot +- `api-integration` - Examples of integrating with various APIs using GitHub Copilot +- `security-best-practices` - Security best practices implementation with GitHub Copilot ## MCP - `mcp` - Examples of Model Context Protocol (MCP) server implementations and custom servers - `github-operation` - GitHub Copilot use cases in GitHub operations with mcp -- `talk-to-database` - GitHub Copilot use cases in sql script generation with mcp +- `talk-to-database` - GitHub Copilot use cases in SQL script generation and database interaction patterns with mcp - `ui-automation` - GitHub Copilot use cases in UI automation script generation - `ui-design-dalle` - GitHub Copilot use cases in image generation automatically using dalle 3 diff --git a/github-copilot-scenario-roles/talk-to-database/complex_query_patterns.sql b/github-copilot-scenario-roles/talk-to-database/complex_query_patterns.sql new file mode 100644 index 0000000..c3c7c37 --- /dev/null +++ b/github-copilot-scenario-roles/talk-to-database/complex_query_patterns.sql @@ -0,0 +1,517 @@ +-- Complex SQL Query Patterns +-- This file contains examples of advanced SQL patterns that GitHub Copilot can help generate and explain +-- Use these examples to learn how to leverage Copilot for complex database operations + +-- 1. Advanced Analytical Query with Window Functions +-- This query analyzes customer purchasing patterns with window functions + +-- Calculate customer purchasing patterns, identifying big spenders and their trends +WITH customer_spending AS ( + SELECT + c.customer_id, + c.first_name, + c.last_name, + COUNT(o.order_id) AS order_count, + SUM(o.total_amount) AS total_spent, + AVG(o.total_amount) AS avg_order_value, + MAX(o.order_date) AS last_order_date, + MIN(o.order_date) AS first_order_date + FROM + customers c + JOIN + orders o ON c.customer_id = o.customer_id + WHERE + o.order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 YEAR) + GROUP BY + c.customer_id, + c.first_name, + c.last_name +), +customer_segments AS ( + SELECT + *, + NTILE(4) OVER (ORDER BY total_spent DESC) AS spending_quartile, + RANK() OVER (ORDER BY total_spent DESC) AS spending_rank, + DENSE_RANK() OVER (ORDER BY order_count DESC) AS frequency_rank, + total_spent / NULLIF(DATEDIFF(last_order_date, first_order_date), 0) * 30 AS monthly_avg_spend, + DATEDIFF(CURRENT_DATE(), last_order_date) AS days_since_last_order + FROM + customer_spending +) +SELECT + customer_id, + first_name, + last_name, + order_count, + total_spent, + avg_order_value, + CASE + WHEN spending_quartile = 1 THEN 'Top Spender' + WHEN spending_quartile = 2 THEN 'High Value' + WHEN spending_quartile = 3 THEN 'Medium Value' + WHEN spending_quartile = 4 THEN 'Low Value' + END AS customer_value_segment, + CASE + WHEN days_since_last_order <= 30 THEN 'Active' + WHEN days_since_last_order <= 90 THEN 'Recent' + WHEN days_since_last_order <= 365 THEN 'Lapsed' + ELSE 'Inactive' + END AS recency_segment, + CASE + WHEN order_count >= 10 THEN 'Frequent' + WHEN order_count >= 5 THEN 'Regular' + ELSE 'Occasional' + END AS frequency_segment, + monthly_avg_spend, + last_order_date, + days_since_last_order +FROM + customer_segments +ORDER BY + total_spent DESC; + +-- 2. Hierarchical Data Query with Common Table Expressions (CTE) +-- Example: Employee organizational hierarchy with recursive CTE + +WITH RECURSIVE employee_hierarchy AS ( + -- Base case: top level managers (employees with no manager) + SELECT + employee_id, + first_name, + last_name, + position, + manager_id, + 0 AS level, + CAST(employee_id AS CHAR(200)) AS path + FROM + employees + WHERE + manager_id IS NULL + + UNION ALL + + -- Recursive case: employees with managers + SELECT + e.employee_id, + e.first_name, + e.last_name, + e.position, + e.manager_id, + eh.level + 1, + CONCAT(eh.path, ',', e.employee_id) AS path + FROM + employees e + JOIN + employee_hierarchy eh ON e.manager_id = eh.employee_id +) +SELECT + employee_id, + CONCAT(REPEAT(' ', level), first_name, ' ', last_name) AS employee_name, + position, + level, + path +FROM + employee_hierarchy +ORDER BY + path; + +-- 3. Dynamic Pivot Table using SQL +-- Convert row data into a dynamic column-based report + +-- For MySQL/MariaDB (using prepared statement approach) +-- This would need to be executed in a stored procedure or application code +-- that can execute dynamic SQL + +-- Sample prepared statement (pseudocode): +/* +SET @sql = NULL; + +-- Get the list of unique categories for columns +SELECT + GROUP_CONCAT(DISTINCT + CONCAT('SUM(CASE WHEN product_category = ''', + product_category, + ''' THEN sales_amount ELSE 0 END) AS `', + product_category, '`') + ) INTO @sql +FROM + sales; + +-- Construct the full pivot query +SET @sql = CONCAT(' + SELECT + DATE_FORMAT(sale_date, ''%Y-%m'') AS month, + ', @sql, ', + SUM(sales_amount) AS total_sales + FROM + sales + GROUP BY + DATE_FORMAT(sale_date, ''%Y-%m'') + ORDER BY + month +'); + +-- Execute the dynamic query +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; +*/ + +-- For PostgreSQL (using crosstab function from tablefunc extension) +-- First, enable the extension: CREATE EXTENSION IF NOT EXISTS tablefunc; + +/* +SELECT * FROM crosstab( + 'SELECT + DATE_FORMAT(sale_date, ''%Y-%m'') AS month, + product_category, + SUM(sales_amount) AS category_sales + FROM + sales + GROUP BY + DATE_FORMAT(sale_date, ''%Y-%m''), + product_category + ORDER BY 1, 2', + 'SELECT DISTINCT product_category FROM sales ORDER BY 1' +) AS ct ( + month text, + "Electronics" numeric, + "Clothing" numeric, + "Food" numeric, + "Home" numeric +); +*/ + +-- 4. Advanced Join Patterns with Multiple Tables +-- Complex reporting query joining multiple tables with different join types + +SELECT + p.product_id, + p.product_name, + p.product_category, + p.unit_price, + COALESCE(SUM(oi.quantity), 0) AS units_sold, + COALESCE(SUM(oi.quantity * oi.unit_price), 0) AS total_revenue, + COUNT(DISTINCT o.customer_id) AS unique_customers, + AVG(r.rating) AS avg_rating, + COUNT(r.review_id) AS review_count, + i.quantity_in_stock, + CASE + WHEN i.quantity_in_stock = 0 THEN 'Out of Stock' + WHEN i.quantity_in_stock < 10 THEN 'Low Stock' + WHEN i.quantity_in_stock < 50 THEN 'Medium Stock' + ELSE 'Well Stocked' + END AS inventory_status, + COALESCE( + NULLIF(SUM(oi.quantity), 0) / + NULLIF(DATEDIFF(MAX(o.order_date), MIN(o.order_date)), 0) * 30, + 0 + ) AS monthly_sales_velocity +FROM + products p +LEFT JOIN + inventory i ON p.product_id = i.product_id +LEFT JOIN + order_items oi ON p.product_id = oi.product_id +LEFT JOIN + orders o ON oi.order_id = o.order_id +LEFT JOIN + reviews r ON p.product_id = r.product_id +WHERE + (o.order_date IS NULL OR o.order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 6 MONTH)) + AND p.is_active = 1 +GROUP BY + p.product_id, + p.product_name, + p.product_category, + p.unit_price, + i.quantity_in_stock +HAVING + units_sold > 0 OR i.quantity_in_stock > 0 +ORDER BY + total_revenue DESC; + +-- 5. Complex Upsert Pattern +-- Upsert (insert or update) pattern for synchronizing data + +-- For MySQL (using ON DUPLICATE KEY UPDATE) +INSERT INTO product_price_history ( + product_id, + price_date, + unit_price, + discount_percent, + effective_price, + modified_by +) +VALUES + (1001, CURRENT_DATE(), 29.99, 0.00, 29.99, 'price_sync'), + (1002, CURRENT_DATE(), 49.99, 10.00, 44.99, 'price_sync'), + (1003, CURRENT_DATE(), 19.99, 5.00, 18.99, 'price_sync') +ON DUPLICATE KEY UPDATE + unit_price = VALUES(unit_price), + discount_percent = VALUES(discount_percent), + effective_price = VALUES(effective_price), + modified_by = VALUES(modified_by), + last_updated = CURRENT_TIMESTAMP(); + +-- For PostgreSQL (using ON CONFLICT) +/* +INSERT INTO product_price_history ( + product_id, + price_date, + unit_price, + discount_percent, + effective_price, + modified_by +) +VALUES + (1001, CURRENT_DATE, 29.99, 0.00, 29.99, 'price_sync'), + (1002, CURRENT_DATE, 49.99, 10.00, 44.99, 'price_sync'), + (1003, CURRENT_DATE, 19.99, 5.00, 18.99, 'price_sync') +ON CONFLICT (product_id, price_date) +DO UPDATE SET + unit_price = EXCLUDED.unit_price, + discount_percent = EXCLUDED.discount_percent, + effective_price = EXCLUDED.effective_price, + modified_by = EXCLUDED.modified_by, + last_updated = CURRENT_TIMESTAMP; +*/ + +-- 6. Temporal Query Patterns +-- Time-based analysis using date functions + +-- Sales trend analysis with moving averages +WITH daily_sales AS ( + SELECT + DATE(order_date) AS sale_date, + SUM(total_amount) AS daily_total + FROM + orders + WHERE + order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 90 DAY) + AND order_status = 'completed' + GROUP BY + DATE(order_date) +), +sales_with_averages AS ( + SELECT + sale_date, + daily_total, + AVG(daily_total) OVER ( + ORDER BY sale_date + ROWS BETWEEN 6 PRECEDING AND CURRENT ROW + ) AS seven_day_average, + AVG(daily_total) OVER ( + ORDER BY sale_date + ROWS BETWEEN 13 PRECEDING AND CURRENT ROW + ) AS fourteen_day_average, + AVG(daily_total) OVER ( + ORDER BY sale_date + ROWS BETWEEN 29 PRECEDING AND CURRENT ROW + ) AS thirty_day_average + FROM + daily_sales +) +SELECT + sale_date, + daily_total, + seven_day_average, + fourteen_day_average, + thirty_day_average, + CASE + WHEN seven_day_average > fourteen_day_average THEN 'Upward Trend' + WHEN seven_day_average < fourteen_day_average THEN 'Downward Trend' + ELSE 'Stable' + END AS short_term_trend, + CASE + WHEN daily_total > seven_day_average THEN 'Above Recent Average' + WHEN daily_total < seven_day_average THEN 'Below Recent Average' + ELSE 'At Average' + END AS daily_performance +FROM + sales_with_averages +ORDER BY + sale_date DESC; + +-- 7. Full-Text Search with Relevance Ranking +-- Demonstrates full-text search capabilities + +-- For MySQL +/* +SELECT + p.product_id, + p.product_name, + p.product_description, + p.product_category, + p.unit_price, + MATCH(p.product_name, p.product_description) AGAINST ('organic natural vitamin' IN BOOLEAN MODE) AS relevance_score +FROM + products p +WHERE + MATCH(p.product_name, p.product_description) AGAINST ('organic natural vitamin' IN BOOLEAN MODE) +ORDER BY + relevance_score DESC +LIMIT 20; +*/ + +-- 8. Conditional Aggregation for Business Intelligence +-- Customer segmentation with conditional aggregation + +SELECT + c.customer_id, + c.first_name, + c.last_name, + c.email, + COUNT(o.order_id) AS total_orders, + SUM(CASE WHEN o.order_status = 'completed' THEN 1 ELSE 0 END) AS completed_orders, + SUM(CASE WHEN o.order_status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_orders, + SUM(CASE WHEN o.payment_method = 'credit_card' THEN o.total_amount ELSE 0 END) AS credit_card_sales, + SUM(CASE WHEN o.payment_method = 'paypal' THEN o.total_amount ELSE 0 END) AS paypal_sales, + SUM(CASE WHEN o.payment_method = 'bank_transfer' THEN o.total_amount ELSE 0 END) AS bank_transfer_sales, + SUM(CASE + WHEN DAYOFWEEK(o.order_date) IN (1, 7) THEN o.total_amount + ELSE 0 + END) AS weekend_sales, + SUM(CASE + WHEN DAYOFWEEK(o.order_date) NOT IN (1, 7) THEN o.total_amount + ELSE 0 + END) AS weekday_sales, + SUM(CASE + WHEN HOUR(o.order_date) BETWEEN 9 AND 17 THEN o.total_amount + ELSE 0 + END) AS business_hours_sales, + SUM(CASE + WHEN HOUR(o.order_date) NOT BETWEEN 9 AND 17 THEN o.total_amount + ELSE 0 + END) AS after_hours_sales, + COUNT(DISTINCT DATE(o.order_date)) AS active_days +FROM + customers c +LEFT JOIN + orders o ON c.customer_id = o.customer_id +WHERE + o.order_date >= DATE_SUB(CURRENT_DATE(), INTERVAL 1 YEAR) +GROUP BY + c.customer_id, + c.first_name, + c.last_name, + c.email +HAVING + total_orders > 0 +ORDER BY + total_orders DESC; + +-- 9. Data Quality Assessment Query +-- Identifies potential data quality issues + +SELECT + 'Customers' AS table_name, + 'Missing Email' AS issue_type, + COUNT(*) AS record_count +FROM + customers +WHERE + email IS NULL OR TRIM(email) = '' + +UNION ALL + +SELECT + 'Customers' AS table_name, + 'Invalid Email Format' AS issue_type, + COUNT(*) AS record_count +FROM + customers +WHERE + email NOT LIKE '%_@_%.__%' + AND email IS NOT NULL + +UNION ALL + +SELECT + 'Orders' AS table_name, + 'Negative Order Amount' AS issue_type, + COUNT(*) AS record_count +FROM + orders +WHERE + total_amount < 0 + +UNION ALL + +SELECT + 'Products' AS table_name, + 'Zero or Negative Price' AS issue_type, + COUNT(*) AS record_count +FROM + products +WHERE + unit_price <= 0 + +UNION ALL + +SELECT + 'Orders' AS table_name, + 'Future Order Date' AS issue_type, + COUNT(*) AS record_count +FROM + orders +WHERE + order_date > CURRENT_DATE() + +UNION ALL + +SELECT + 'Order Items' AS table_name, + 'Quantity Mismatch' AS issue_type, + COUNT(*) AS record_count +FROM + order_items oi +JOIN + orders o ON oi.order_id = o.order_id +WHERE + oi.quantity <= 0 + OR (oi.unit_price * oi.quantity) <> oi.line_total + +ORDER BY + record_count DESC; + +-- 10. Report on Data Retention and Performance Optimization +-- Identifies tables for archiving and optimization + +SELECT + table_schema AS database_name, + table_name, + table_rows, + ROUND((data_length + index_length) / 1024 / 1024, 2) AS size_mb, + ROUND((data_length) / 1024 / 1024, 2) AS data_size_mb, + ROUND((index_length) / 1024 / 1024, 2) AS index_size_mb, + ROUND(index_length / CASE WHEN data_length = 0 THEN 1 ELSE data_length END * 100, 2) AS index_ratio_percent, + CASE + WHEN table_name LIKE '%_archive' THEN 'Archive' + WHEN table_name LIKE '%_log' THEN 'Log' + WHEN table_name LIKE '%_history' THEN 'History' + WHEN table_name LIKE '%_audit' THEN 'Audit' + ELSE 'Operational' + END AS table_type, + ( + SELECT MAX(update_time) + FROM information_schema.tables + WHERE table_schema = information_schema.tables.table_schema + AND table_name = information_schema.tables.table_name + ) AS last_update, + DATEDIFF(NOW(), ( + SELECT MAX(update_time) + FROM information_schema.tables + WHERE table_schema = information_schema.tables.table_schema + AND table_name = information_schema.tables.table_name + )) AS days_since_update +FROM + information_schema.tables +WHERE + table_schema = 'your_database_name' + AND table_type = 'BASE TABLE' +ORDER BY + size_mb DESC; + +-- Note: Some of these queries may need adjustments based on your specific database engine +-- They are designed to work primarily with MySQL/MariaDB but concepts apply to other databases \ No newline at end of file diff --git a/github-copilot-scenario-roles/talk-to-database/orm_patterns.js b/github-copilot-scenario-roles/talk-to-database/orm_patterns.js new file mode 100644 index 0000000..191012c --- /dev/null +++ b/github-copilot-scenario-roles/talk-to-database/orm_patterns.js @@ -0,0 +1,660 @@ +// Database Interaction Patterns with Object-Relational Mapping (ORM) +// This file demonstrates various patterns for working with databases using ORMs +// with GitHub Copilot assistance + +// Example using Sequelize ORM (Node.js) + +const { Sequelize, DataTypes, Op } = require('sequelize'); +const sequelize = new Sequelize('database', 'username', 'password', { + host: 'localhost', + dialect: 'mysql', + logging: false, + pool: { + max: 10, + min: 0, + acquire: 30000, + idle: 10000 + } +}); + +// Define models +const User = sequelize.define('User', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + username: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + len: [3, 50] + } + }, + email: { + type: DataTypes.STRING, + allowNull: false, + unique: true, + validate: { + isEmail: true + } + }, + password: { + type: DataTypes.STRING, + allowNull: false + }, + role: { + type: DataTypes.ENUM('user', 'admin', 'moderator'), + defaultValue: 'user' + }, + lastLogin: { + type: DataTypes.DATE + }, + isActive: { + type: DataTypes.BOOLEAN, + defaultValue: true + } +}, { + timestamps: true, + paranoid: true, // Soft deletes + indexes: [ + { fields: ['email'] }, + { fields: ['username'] }, + { fields: ['role', 'isActive'] } + ] +}); + +const Post = sequelize.define('Post', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + title: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: [3, 200] + } + }, + content: { + type: DataTypes.TEXT, + allowNull: false + }, + status: { + type: DataTypes.ENUM('draft', 'published', 'archived'), + defaultValue: 'draft' + }, + publishedAt: { + type: DataTypes.DATE + }, + viewCount: { + type: DataTypes.INTEGER, + defaultValue: 0 + } +}, { + timestamps: true, + indexes: [ + { fields: ['status'] }, + { fields: ['publishedAt'] } + ] +}); + +const Comment = sequelize.define('Comment', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + content: { + type: DataTypes.TEXT, + allowNull: false + }, + isApproved: { + type: DataTypes.BOOLEAN, + defaultValue: false + } +}, { + timestamps: true +}); + +const Category = sequelize.define('Category', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, + slug: { + type: DataTypes.STRING, + allowNull: false, + unique: true + } +}, { + timestamps: true +}); + +const Tag = sequelize.define('Tag', { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true + }, + name: { + type: DataTypes.STRING, + allowNull: false, + unique: true + }, + slug: { + type: DataTypes.STRING, + allowNull: false, + unique: true + } +}, { + timestamps: true +}); + +// Define relationships +User.hasMany(Post, { as: 'posts', foreignKey: 'authorId' }); +Post.belongsTo(User, { as: 'author', foreignKey: 'authorId' }); + +Post.hasMany(Comment, { as: 'comments', foreignKey: 'postId' }); +Comment.belongsTo(Post, { as: 'post', foreignKey: 'postId' }); + +User.hasMany(Comment, { as: 'comments', foreignKey: 'userId' }); +Comment.belongsTo(User, { as: 'user', foreignKey: 'userId' }); + +Post.belongsTo(Category, { as: 'category', foreignKey: 'categoryId' }); +Category.hasMany(Post, { as: 'posts', foreignKey: 'categoryId' }); + +// Many-to-many relationship between Post and Tag +const PostTag = sequelize.define('PostTag', {}, { timestamps: false }); +Post.belongsToMany(Tag, { through: PostTag }); +Tag.belongsToMany(Post, { through: PostTag }); + +// Database interaction patterns examples + +/** + * Pattern: Repository Pattern + * Encapsulates database operations for a specific model + */ +class UserRepository { + /** + * Create a new user + * @param {Object} userData - User data + * @returns {Promise} - Created user + */ + async create(userData) { + try { + return await User.create(userData); + } catch (error) { + console.error('Error creating user:', error); + throw error; + } + } + + /** + * Find a user by ID + * @param {string} id - User ID + * @returns {Promise} - Found user or null + */ + async findById(id) { + return await User.findByPk(id); + } + + /** + * Find a user by email + * @param {string} email - User email + * @returns {Promise} - Found user or null + */ + async findByEmail(email) { + return await User.findOne({ where: { email } }); + } + + /** + * Find users by role + * @param {string} role - User role + * @param {Object} options - Query options + * @returns {Promise>} - Found users + */ + async findByRole(role, options = {}) { + const { page = 1, limit = 10, isActive = true } = options; + const offset = (page - 1) * limit; + + return await User.findAndCountAll({ + where: { role, isActive }, + limit, + offset, + order: [['createdAt', 'DESC']] + }); + } + + /** + * Update a user + * @param {string} id - User ID + * @param {Object} updates - User updates + * @returns {Promise} - Updated user + */ + async update(id, updates) { + const [updated, users] = await User.update(updates, { + where: { id }, + returning: true + }); + + return updated ? users[0] : null; + } + + /** + * Delete a user (soft delete) + * @param {string} id - User ID + * @returns {Promise} - Success status + */ + async delete(id) { + const deleted = await User.destroy({ + where: { id } + }); + + return deleted > 0; + } + + /** + * Search users + * @param {Object} criteria - Search criteria + * @param {Object} options - Query options + * @returns {Promise} - Users and count + */ + async search(criteria, options = {}) { + const { page = 1, limit = 10 } = options; + const offset = (page - 1) * limit; + + const whereClause = {}; + + if (criteria.username) { + whereClause.username = { [Op.like]: `%${criteria.username}%` }; + } + + if (criteria.email) { + whereClause.email = { [Op.like]: `%${criteria.email}%` }; + } + + if (criteria.role) { + whereClause.role = criteria.role; + } + + return await User.findAndCountAll({ + where: whereClause, + limit, + offset, + order: [['createdAt', 'DESC']] + }); + } +} + +/** + * Pattern: Unit of Work + * Coordinates transactions across multiple repositories + */ +class UnitOfWork { + constructor(sequelize) { + this.sequelize = sequelize; + this.userRepository = new UserRepository(); + this.postRepository = null; // Add other repositories as needed + } + + /** + * Execute work within a transaction + * @param {Function} work - Function containing the work to do + * @returns {Promise} - Result of the work + */ + async executeInTransaction(work) { + const t = await this.sequelize.transaction(); + + try { + const result = await work(t); + await t.commit(); + return result; + } catch (error) { + await t.rollback(); + throw error; + } + } +} + +/** + * Pattern: Query Builder + * Builds complex queries with a fluent interface + */ +class PostQueryBuilder { + constructor() { + this.whereClause = {}; + this.includeModels = []; + this.pagination = { page: 1, limit: 10 }; + this.ordering = [['createdAt', 'DESC']]; + } + + withStatus(status) { + this.whereClause.status = status; + return this; + } + + withAuthor(authorId) { + this.whereClause.authorId = authorId; + return this; + } + + withCategory(categoryId) { + this.whereClause.categoryId = categoryId; + return this; + } + + publishedBetween(startDate, endDate) { + this.whereClause.publishedAt = { + [Op.between]: [startDate, endDate] + }; + return this; + } + + publishedAfter(date) { + this.whereClause.publishedAt = { + ...this.whereClause.publishedAt, + [Op.gte]: date + }; + return this; + } + + withMinimumViews(count) { + this.whereClause.viewCount = { + [Op.gte]: count + }; + return this; + } + + withAuthorIncluded() { + this.includeModels.push({ + model: User, + as: 'author', + attributes: ['id', 'username', 'email'] + }); + return this; + } + + withCategoryIncluded() { + this.includeModels.push({ + model: Category, + as: 'category' + }); + return this; + } + + withCommentsIncluded(approvedOnly = true) { + let include = { + model: Comment, + as: 'comments' + }; + + if (approvedOnly) { + include.where = { isApproved: true }; + } + + this.includeModels.push(include); + return this; + } + + withTagsIncluded() { + this.includeModels.push({ + model: Tag, + through: { attributes: [] } // Exclude junction table + }); + return this; + } + + paginate(page, limit) { + this.pagination = { page, limit }; + return this; + } + + orderBy(field, direction = 'DESC') { + this.ordering = [[field, direction]]; + return this; + } + + async execute() { + const { page, limit } = this.pagination; + const offset = (page - 1) * limit; + + return await Post.findAndCountAll({ + where: this.whereClause, + include: this.includeModels, + limit, + offset, + order: this.ordering, + distinct: true // Important for correct count with includes + }); + } +} + +/** + * Pattern: Service Layer + * Implements business logic using repositories + */ +class BlogService { + constructor() { + this.unitOfWork = new UnitOfWork(sequelize); + } + + /** + * Create a new blog post with tags + * @param {Object} postData - Post data + * @param {string[]} tagIds - Tag IDs + * @returns {Promise} - Created post + */ + async createPost(postData, tagIds = []) { + return await this.unitOfWork.executeInTransaction(async (transaction) => { + // Create the post + const post = await Post.create(postData, { transaction }); + + // Add tags if any + if (tagIds.length > 0) { + await post.setTags(tagIds, { transaction }); + } + + // Return the created post with its tags + return await Post.findByPk(post.id, { + include: [ + { model: Tag }, + { model: User, as: 'author' } + ], + transaction + }); + }); + } + + /** + * Publish a blog post + * @param {string} postId - Post ID + * @returns {Promise} - Updated post + */ + async publishPost(postId) { + return await this.unitOfWork.executeInTransaction(async (transaction) => { + const post = await Post.findByPk(postId, { transaction }); + + if (!post) { + throw new Error(`Post with ID ${postId} not found`); + } + + post.status = 'published'; + post.publishedAt = new Date(); + await post.save({ transaction }); + + return post; + }); + } + + /** + * Add a comment to a post + * @param {string} postId - Post ID + * @param {string} userId - User ID + * @param {string} content - Comment content + * @param {boolean} autoApprove - Auto approve comment + * @returns {Promise} - Created comment + */ + async addComment(postId, userId, content, autoApprove = false) { + return await this.unitOfWork.executeInTransaction(async (transaction) => { + // Check if post exists + const post = await Post.findByPk(postId, { transaction }); + if (!post) { + throw new Error(`Post with ID ${postId} not found`); + } + + // Create comment + const comment = await Comment.create({ + content, + postId, + userId, + isApproved: autoApprove + }, { transaction }); + + return comment; + }); + } + + /** + * Get featured posts for homepage + * @returns {Promise>} - Featured posts + */ + async getFeaturedPosts() { + const queryBuilder = new PostQueryBuilder() + .withStatus('published') + .withMinimumViews(100) + .withAuthorIncluded() + .withCategoryIncluded() + .orderBy('viewCount', 'DESC') + .paginate(1, 5); + + const result = await queryBuilder.execute(); + return result.rows; + } + + /** + * Search posts with advanced criteria + * @param {Object} criteria - Search criteria + * @returns {Promise} - Posts and count + */ + async searchPosts(criteria) { + const queryBuilder = new PostQueryBuilder() + .withStatus('published') + .withAuthorIncluded() + .withCategoryIncluded(); + + if (criteria.categoryId) { + queryBuilder.withCategory(criteria.categoryId); + } + + if (criteria.authorId) { + queryBuilder.withAuthor(criteria.authorId); + } + + if (criteria.startDate && criteria.endDate) { + queryBuilder.publishedBetween(criteria.startDate, criteria.endDate); + } else if (criteria.startDate) { + queryBuilder.publishedAfter(criteria.startDate); + } + + if (criteria.includeTags) { + queryBuilder.withTagsIncluded(); + } + + if (criteria.includeComments) { + queryBuilder.withCommentsIncluded(true); + } + + queryBuilder.paginate(criteria.page || 1, criteria.limit || 10); + + if (criteria.orderBy) { + queryBuilder.orderBy(criteria.orderBy, criteria.orderDirection || 'DESC'); + } + + return await queryBuilder.execute(); + } +} + +// Example usage +async function exampleUsage() { + // Initialize database connection + try { + await sequelize.authenticate(); + console.log('Database connection established.'); + + // Sync models with database (in development only) + await sequelize.sync({ force: false }); + + // Create user repository + const userRepo = new UserRepository(); + + // Create a new user + const user = await userRepo.create({ + username: 'johndoe', + email: 'john@example.com', + password: 'hashedpassword123', // Should be properly hashed in real code + role: 'admin' + }); + console.log('User created:', user.id); + + // Create blog service + const blogService = new BlogService(); + + // Create a blog post with tags + const post = await blogService.createPost({ + title: 'Understanding ORMs', + content: 'This is a detailed post about Object-Relational Mapping...', + authorId: user.id + }, ['tag1', 'tag2']); + console.log('Post created:', post.id); + + // Publish the post + await blogService.publishPost(post.id); + console.log('Post published'); + + // Search for posts + const searchResults = await blogService.searchPosts({ + authorId: user.id, + includeTags: true, + includeComments: true, + page: 1, + limit: 10 + }); + console.log('Search results:', searchResults.count); + + } catch (error) { + console.error('Error:', error); + } finally { + await sequelize.close(); + } +} + +// Uncomment to run example: +// exampleUsage(); + +module.exports = { + sequelize, + models: { + User, + Post, + Comment, + Category, + Tag + }, + repositories: { + UserRepository + }, + services: { + BlogService + }, + UnitOfWork, + PostQueryBuilder +}; \ No newline at end of file diff --git a/github-copilot-scenario-roles/ui-automation/test_strategy_patterns.js b/github-copilot-scenario-roles/ui-automation/test_strategy_patterns.js new file mode 100644 index 0000000..2a7fb82 --- /dev/null +++ b/github-copilot-scenario-roles/ui-automation/test_strategy_patterns.js @@ -0,0 +1,600 @@ +// Advanced UI Testing Strategy Patterns +// This file demonstrates more advanced UI test automation patterns +// Use GitHub Copilot to help implement and extend these patterns + +const { Builder, By, Key, until } = require('selenium-webdriver'); +const assert = require('assert'); + +/** + * Page Object Pattern - Base Page + * Provides common functionality for all page objects + */ +class BasePage { + constructor(driver) { + this.driver = driver; + this.timeout = 10000; // Default timeout + } + + /** + * Navigate to a specific URL + * @param {string} url - URL to navigate to + * @returns {Promise} + */ + async navigate(url) { + await this.driver.get(url); + } + + /** + * Find an element using various locator strategies + * @param {Object} locator - Element locator + * @returns {Promise} + */ + async findElement(locator) { + return await this.driver.findElement(locator); + } + + /** + * Find multiple elements + * @param {Object} locator - Elements locator + * @returns {Promise} + */ + async findElements(locator) { + return await this.driver.findElements(locator); + } + + /** + * Wait for an element to be visible + * @param {Object} locator - Element locator + * @param {number} timeout - Wait timeout + * @returns {Promise} + */ + async waitForElementVisible(locator, timeout = this.timeout) { + return await this.driver.wait(until.elementLocated(locator), timeout); + } + + /** + * Wait for an element to be clickable + * @param {Object} locator - Element locator + * @param {number} timeout - Wait timeout + * @returns {Promise} + */ + async waitForElementClickable(locator, timeout = this.timeout) { + const element = await this.waitForElementVisible(locator, timeout); + return await this.driver.wait(until.elementIsEnabled(element), timeout); + } + + /** + * Click an element + * @param {Object} locator - Element locator + * @returns {Promise} + */ + async click(locator) { + const element = await this.waitForElementClickable(locator); + await element.click(); + } + + /** + * Type text into an input field + * @param {Object} locator - Element locator + * @param {string} text - Text to type + * @returns {Promise} + */ + async type(locator, text) { + const element = await this.waitForElementVisible(locator); + await element.clear(); + await element.sendKeys(text); + } + + /** + * Get text from an element + * @param {Object} locator - Element locator + * @returns {Promise} + */ + async getText(locator) { + const element = await this.waitForElementVisible(locator); + return await element.getText(); + } + + /** + * Check if an element exists + * @param {Object} locator - Element locator + * @returns {Promise} + */ + async isElementPresent(locator) { + try { + await this.driver.findElement(locator); + return true; + } catch (error) { + return false; + } + } + + /** + * Take a screenshot and save it + * @param {string} fileName - File name for the screenshot + * @returns {Promise} + */ + async takeScreenshot(fileName) { + const screenshot = await this.driver.takeScreenshot(); + require('fs').writeFileSync(fileName, screenshot, 'base64'); + } +} + +/** + * Page Object Pattern - Login Page + */ +class LoginPage extends BasePage { + constructor(driver) { + super(driver); + // Define page elements + this.usernameInput = By.id('username'); + this.passwordInput = By.id('password'); + this.loginButton = By.css('button[type="submit"]'); + this.errorMessage = By.className('error-message'); + this.forgotPasswordLink = By.linkText('Forgot Password?'); + } + + /** + * Navigate to login page + * @returns {Promise} + */ + async navigateToLoginPage() { + await this.navigate('https://example.com/login'); + } + + /** + * Perform login + * @param {string} username - Username + * @param {string} password - Password + * @returns {Promise} + */ + async login(username, password) { + await this.type(this.usernameInput, username); + await this.type(this.passwordInput, password); + await this.click(this.loginButton); + } + + /** + * Get error message text + * @returns {Promise} + */ + async getErrorMessage() { + if (await this.isElementPresent(this.errorMessage)) { + return await this.getText(this.errorMessage); + } + return ''; + } + + /** + * Click forgot password link + * @returns {Promise} + */ + async clickForgotPassword() { + await this.click(this.forgotPasswordLink); + } +} + +/** + * Page Object Pattern - Dashboard Page + */ +class DashboardPage extends BasePage { + constructor(driver) { + super(driver); + // Define page elements + this.welcomeMessage = By.className('welcome-message'); + this.userProfileLink = By.id('user-profile'); + this.logoutButton = By.id('logout'); + this.navigationMenu = By.id('main-nav'); + this.notificationIcon = By.className('notifications'); + this.notificationCount = By.css('.notifications .count'); + } + + /** + * Check if user is logged in + * @returns {Promise} + */ + async isLoggedIn() { + return await this.isElementPresent(this.welcomeMessage); + } + + /** + * Get welcome message text + * @returns {Promise} + */ + async getWelcomeMessage() { + return await this.getText(this.welcomeMessage); + } + + /** + * Click on a specific navigation item + * @param {string} navItemText - Navigation item text + * @returns {Promise} + */ + async navigateTo(navItemText) { + const navItemLocator = By.xpath(`//nav[@id='main-nav']//a[text()='${navItemText}']`); + await this.click(navItemLocator); + } + + /** + * Get notification count + * @returns {Promise} + */ + async getNotificationCount() { + if (await this.isElementPresent(this.notificationCount)) { + const countText = await this.getText(this.notificationCount); + return parseInt(countText, 10) || 0; + } + return 0; + } + + /** + * Logout + * @returns {Promise} + */ + async logout() { + await this.click(this.logoutButton); + } +} + +/** + * Strategy Pattern - Test Data Strategy + */ +class TestDataStrategy { + /** + * Get test data for a specific test case + * @param {string} testCase - Test case identifier + * @returns {Object} - Test data + */ + getTestData(testCase) { + throw new Error('Method not implemented'); + } +} + +/** + * Concrete Strategy - Hardcoded Test Data + */ +class HardcodedTestDataStrategy extends TestDataStrategy { + getTestData(testCase) { + const testDataMap = { + 'validLogin': { + username: 'testuser', + password: 'Password123', + expectedMessage: 'Welcome, Test User!' + }, + 'invalidLogin': { + username: 'testuser', + password: 'wrongpassword', + expectedError: 'Invalid username or password' + }, + 'emptyCredentials': { + username: '', + password: '', + expectedError: 'Username and password are required' + } + }; + + return testDataMap[testCase] || {}; + } +} + +/** + * Concrete Strategy - CSV Test Data + */ +class CsvTestDataStrategy extends TestDataStrategy { + constructor(filePath) { + super(); + this.filePath = filePath; + this.testData = this.loadTestData(); + } + + loadTestData() { + // In a real implementation, this would parse a CSV file + // For this example, we'll return a mock object + return { + 'validLogin': { + username: 'csvuser', + password: 'CsvPassword123', + expectedMessage: 'Welcome, CSV User!' + }, + 'invalidLogin': { + username: 'csvuser', + password: 'wrongpassword', + expectedError: 'Invalid username or password' + } + }; + } + + getTestData(testCase) { + return this.testData[testCase] || {}; + } +} + +/** + * Concrete Strategy - API Test Data + */ +class ApiTestDataStrategy extends TestDataStrategy { + constructor(apiUrl) { + super(); + this.apiUrl = apiUrl; + } + + async getTestData(testCase) { + // In a real implementation, this would make an API call + // For this example, we'll return a mock object + return { + 'validLogin': { + username: 'apiuser', + password: 'ApiPassword123', + expectedMessage: 'Welcome, API User!' + } + }; + } +} + +/** + * Command Pattern - UI Commands + */ +class UICommand { + execute() { + throw new Error('Method not implemented'); + } +} + +/** + * Concrete Command - Login Command + */ +class LoginCommand extends UICommand { + constructor(loginPage, username, password) { + super(); + this.loginPage = loginPage; + this.username = username; + this.password = password; + } + + async execute() { + await this.loginPage.navigateToLoginPage(); + await this.loginPage.login(this.username, this.password); + } +} + +/** + * Concrete Command - Logout Command + */ +class LogoutCommand extends UICommand { + constructor(dashboardPage) { + super(); + this.dashboardPage = dashboardPage; + } + + async execute() { + await this.dashboardPage.logout(); + } +} + +/** + * Observer Pattern - Test Event Listener + */ +class TestEventListener { + onTestStart(testName) {} + onTestPass(testName, duration) {} + onTestFail(testName, error) {} + onScreenshot(testName, screenshotPath) {} +} + +/** + * Concrete Observer - Console Reporter + */ +class ConsoleReporter extends TestEventListener { + onTestStart(testName) { + console.log(`[TEST START] ${testName}`); + } + + onTestPass(testName, duration) { + console.log(`[TEST PASS] ${testName} (${duration}ms)`); + } + + onTestFail(testName, error) { + console.error(`[TEST FAIL] ${testName}: ${error.message}`); + } + + onScreenshot(testName, screenshotPath) { + console.log(`[SCREENSHOT] ${testName}: ${screenshotPath}`); + } +} + +/** + * Test Runner with advanced patterns + */ +class AdvancedTestRunner { + constructor(testDataStrategy) { + this.driver = null; + this.testDataStrategy = testDataStrategy; + this.listeners = []; + } + + /** + * Add a test event listener + * @param {TestEventListener} listener - Event listener + */ + addListener(listener) { + this.listeners.push(listener); + } + + /** + * Notify all listeners of test start + * @param {string} testName - Test name + */ + notifyTestStart(testName) { + this.listeners.forEach(listener => listener.onTestStart(testName)); + } + + /** + * Notify all listeners of test pass + * @param {string} testName - Test name + * @param {number} duration - Test duration in ms + */ + notifyTestPass(testName, duration) { + this.listeners.forEach(listener => listener.onTestPass(testName, duration)); + } + + /** + * Notify all listeners of test failure + * @param {string} testName - Test name + * @param {Error} error - Test error + */ + notifyTestFail(testName, error) { + this.listeners.forEach(listener => listener.onTestFail(testName, error)); + } + + /** + * Notify all listeners of screenshot + * @param {string} testName - Test name + * @param {string} screenshotPath - Screenshot path + */ + notifyScreenshot(testName, screenshotPath) { + this.listeners.forEach(listener => listener.onScreenshot(testName, screenshotPath)); + } + + /** + * Set up test environment + */ + async setup() { + this.driver = await new Builder().forBrowser('chrome').build(); + await this.driver.manage().window().maximize(); + await this.driver.manage().setTimeouts({ implicit: 5000 }); + } + + /** + * Tear down test environment + */ + async teardown() { + if (this.driver) { + await this.driver.quit(); + } + } + + /** + * Take a screenshot on failure + * @param {string} testName - Test name + */ + async takeScreenshotOnFailure(testName) { + const screenshotPath = `./screenshots/${testName}_${Date.now()}.png`; + await this.driver.takeScreenshot() + .then(data => { + require('fs').writeFileSync(screenshotPath, data, 'base64'); + this.notifyScreenshot(testName, screenshotPath); + }) + .catch(err => console.error('Failed to take screenshot:', err)); + } + + /** + * Run a test with timing and reporting + * @param {string} testName - Test name + * @param {Function} testFn - Test function + */ + async runTest(testName, testFn) { + this.notifyTestStart(testName); + const startTime = Date.now(); + + try { + await testFn(); + const duration = Date.now() - startTime; + this.notifyTestPass(testName, duration); + } catch (error) { + await this.takeScreenshotOnFailure(testName); + this.notifyTestFail(testName, error); + throw error; + } + } + + /** + * Run all tests + */ + async runAllTests() { + try { + await this.setup(); + + // Create page objects + const loginPage = new LoginPage(this.driver); + const dashboardPage = new DashboardPage(this.driver); + + // Run test cases + await this.runTest('validLoginTest', async () => { + const testData = this.testDataStrategy.getTestData('validLogin'); + + // Use command pattern + const loginCommand = new LoginCommand(loginPage, testData.username, testData.password); + await loginCommand.execute(); + + // Verify successful login + assert.strictEqual( + await dashboardPage.isLoggedIn(), + true, + 'User should be logged in' + ); + + const welcomeMessage = await dashboardPage.getWelcomeMessage(); + assert.strictEqual( + welcomeMessage, + testData.expectedMessage, + 'Welcome message should match expected message' + ); + + // Use command pattern for logout + const logoutCommand = new LogoutCommand(dashboardPage); + await logoutCommand.execute(); + }); + + await this.runTest('invalidLoginTest', async () => { + const testData = this.testDataStrategy.getTestData('invalidLogin'); + + await loginPage.navigateToLoginPage(); + await loginPage.login(testData.username, testData.password); + + const errorMessage = await loginPage.getErrorMessage(); + assert.strictEqual( + errorMessage, + testData.expectedError, + 'Error message should match expected message' + ); + }); + + } finally { + await this.teardown(); + } + } +} + +// Example usage +async function main() { + // Use hardcoded test data strategy + const testDataStrategy = new HardcodedTestDataStrategy(); + + // Create test runner + const testRunner = new AdvancedTestRunner(testDataStrategy); + + // Add console reporter + testRunner.addListener(new ConsoleReporter()); + + // Run all tests + await testRunner.runAllTests(); +} + +// Uncomment to run: +// main().catch(console.error); + +module.exports = { + BasePage, + LoginPage, + DashboardPage, + TestDataStrategy, + HardcodedTestDataStrategy, + CsvTestDataStrategy, + ApiTestDataStrategy, + UICommand, + LoginCommand, + LogoutCommand, + TestEventListener, + ConsoleReporter, + AdvancedTestRunner +}; \ No newline at end of file