Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Password Generator & Sports API Feature Creation #1175

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions jarviscli/plugins/passGen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import secrets
import string
from plugin import plugin, require, alias

@require(network=False)
@alias("generate password")
@plugin("password generator")
class PasswordGenerator:
"""
This plugin generates a strong, random password with a specified length and choice of character types:
uppercase, lowercase, digits, and special characters.
"""

def __call__(self, jarvis, s):
self.generate_password(jarvis)

def generate_password(self, jarvis):
jarvis.say("Welcome to the Password Generator!")
length = jarvis.input_number("Enter the desired password length (minimum 12): ", rtype=float)
if length is None or length < 12:
jarvis.say("Invalid length. Password length must be at least 12 characters for security.")
return

include_uppercase = jarvis.ask_yes_no("Include uppercase letters (A-Z)? (yes/no): ")
include_lowercase = jarvis.ask_yes_no("Include lowercase letters (a-z)? (yes/no): ")
include_digits = jarvis.ask_yes_no("Include digits (0-9)? (yes/no): ")
include_specials = jarvis.ask_yes_no("Include special characters (e.g., @#$%)? (yes/no): ")

if not any([include_uppercase, include_lowercase, include_digits, include_specials]):
jarvis.say("At least one character type must be selected!")
return

password = self.create_password(int(length), include_uppercase, include_lowercase, include_digits, include_specials)
jarvis.say(f"Generated Password: {password}", color='green')

def create_password(self, length, include_uppercase, include_lowercase, include_digits, include_specials):
char_pool = []
if include_uppercase:
char_pool.append(string.ascii_uppercase)
if include_lowercase:
char_pool.append(string.ascii_lowercase)
if include_digits:
char_pool.append(string.digits)
if include_specials:
char_pool.append(string.punctuation)

if not char_pool:
return ''

# Ensuring each character type selected is used at least once
password_chars = [secrets.choice(chars) for chars in char_pool]
password_chars += [secrets.choice(''.join(char_pool)) for _ in range(length - len(password_chars))]

secrets.SystemRandom().shuffle(password_chars) # Use SystemRandom for better entropy
return ''.join(password_chars)
55 changes: 55 additions & 0 deletions jarviscli/plugins/passGen_documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
Documentation for passGen.py
Overview
passGen.py contains a class PasswordGenerator designed for Jarvis, a plugin-based command-line tool. This plugin allows users to generate strong, random passwords directly through the command line interface. It supports various customizable parameters such as password length and character types including uppercase, lowercase, digits, and special characters.

Requirements
Python 3.6+
The Jarvis command line interface setup
secrets and string libraries (standard in Python 3.6+)
Installation
No additional installation is necessary beyond setting up the Jarvis framework and ensuring that Python 3.6 or newer is installed on your system. Simply place passGen.py in the Jarvis plugins directory, typically found at jarviscli/plugins.

Usage
To use the PasswordGenerator plugin, ensure it is loaded in the Jarvis framework, and initiate it through the Jarvis command line interface with the following command:

generate password
This will activate the plugin and start the password generation process.

Class and Methods
PasswordGenerator

Purpose: Provides functionalities to generate a secure, random password based on user-specified criteria.
Methods:
__call__(self, jarvis, s): Entry point method called by the Jarvis framework when the plugin is activated. It delegates the main operation to generate_password.
generate_password(self, jarvis): Interacts with the user to specify password criteria and initiates the password creation process.
create_password(self, length, include_uppercase, include_lowercase, include_digits, include_specials): Constructs a password based on specified parameters ensuring randomness and security through cryptographic methods.
Detailed Method Description
generate_password(self, jarvis)

Parameters:
jarvis: Instance of JarvisAPI, used to interact with the user.
Functionality:
Prompts the user for the desired password length and character types.
Validates the user inputs and ensures security standards are met.
Calls create_password to generate the password and outputs the result.
create_password(self, length, include_uppercase, include_lowercase, include_digits, include_specials)

Parameters:
length (int): The desired length of the password.
include_uppercase (bool): Whether to include uppercase letters.
include_lowercase (bool): Whether to include lowercase letters.
include_digits (bool): Whether to include numeric digits.
include_specials (bool): Whether to include special characters.
Functionality:
Builds a character pool from the selected character types.
Ensures that each selected character type is represented at least once.
Randomly constructs the password from the character pool using cryptographically secure methods and shuffles it for additional randomness.
Returns the generated password.
Notes
This plugin uses the secrets module for secure random number generation, suitable for cryptographic use, ensuring that the generated passwords are both strong and secure.

Example
To generate a password with all character types, 20 characters long:

generate password
Follow the prompts to specify length and character inclusions.
31 changes: 31 additions & 0 deletions jarviscli/plugins/soccerApi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import requests
from plugin import plugin, alias, require
from colorama import Fore

@require(network=True)
@alias("soccer")
@plugin("soccer scores")
class SoccerScores:
"""The user can request the latest soccer scores, and the API from TheSportsDB will be used to make a GET request and
fetch data. Then, the user can see the latest scores from major soccer games."""

def __call__(self, jarvis: "JarvisAPI", s: str) -> None:
self.print_latest_scores(jarvis)

def print_latest_scores(self, jarvis: "JarvisAPI"):
jarvis.say("Fetching the latest soccer scores...")
try:
response = requests.get('https://www.thesportsdb.com/api/v1/json/3/latestsoccer.php')
response.raise_for_status()
data = response.json()

if 'games' in data:
for game in data['games']:
jarvis.say(f"Match: {game['strEvent']}", color=Fore.BLUE)
jarvis.say(f"Date: {game['dateEvent']}")
jarvis.say(f"Score: {game['intHomeScore']} - {game['intAwayScore']}", color=Fore.GREEN)
jarvis.say(f"Status: {game['strStatus']}\n")
else:
jarvis.say("No recent games data found.", color=Fore.RED)
except requests.RequestException as e:
jarvis.say(f"An error occurred while fetching data: {e}", color=Fore.RED)
40 changes: 40 additions & 0 deletions jarviscli/plugins/soccerApi_documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
Overview
The SoccerScores plugin is designed to fetch and display the latest soccer scores from major games using TheSportsDB API. This plugin is integrated into the Jarvis command-line interface, allowing users to get real-time soccer game updates directly from their terminal.

Requirements
Python 3.x
The requests library installed (pip install requests)
The Jarvis command-line interface setup
An active internet connection (as this plugin requires network access)
Installation
This plugin should be placed in the Jarvis plugins directory. No additional installation steps are needed beyond ensuring that all dependencies (like the requests library) are installed. Here is how you can place it:

Navigate to the Jarvis plugins directory (typically found at jarviscli/plugins).
Add the SoccerScores.py file to this directory.
Usage
To use the SoccerScores plugin within Jarvis, invoke the plugin through the command line:

soccer
This command is set up via the @alias("soccer") decorator, making it easy to remember and use.

Class and Methods
SoccerScores

Purpose: Fetches and displays live soccer scores from TheSportsDB API.
Methods:
__call__(self, jarvis, s): This method is the entry point for the Jarvis plugin activation. It calls the print_latest_scores method.
print_latest_scores(self, jarvis): Handles the communication with TheSportsDB API, processes the JSON data, and outputs the soccer scores to the user.
Detailed Method Description
print_latest_scores(self, jarvis)

Parameters:
jarvis: Instance of JarvisAPI, used to interact with the user.
Functionality:
Makes an HTTP GET request to TheSportsDB API to fetch the latest soccer scores.
Parses the JSON response and formats the output to display match details such as event, date, score, and status.
Handles potential network errors gracefully and informs the user if any issues occur or if no data is found.
Example
To check the latest soccer scores, simply type the following command in the Jarvis interface:

soccer
Follow the interactive prompts if any, and view the latest scores as they are fetched and displayed.
58 changes: 58 additions & 0 deletions jarviscli/tests/test_passGen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import unittest
from unittest.mock import patch, MagicMock
from your_module import PasswordGenerator, JarvisAPI # Adjust import according to your project structure

class TestPasswordGenerator(unittest.TestCase):
def setUp(self):
self.jarvis = MagicMock(spec=JarvisAPI)
self.jarvis.input_number = MagicMock()
self.jarvis.ask_yes_no = MagicMock()
self.password_generator = PasswordGenerator()

def mock_inputs(self, length, upper, lower, digits, specials):
self.jarvis.input_number.return_value = length
self.jarvis.ask_yes_no.side_effect = [upper, lower, digits, specials]

@patch('secrets.choice')
@patch('secrets.SystemRandom.shuffle')
def test_valid_password_generation(self, mock_shuffle, mock_choice):
self.mock_inputs(12, 'yes', 'yes', 'yes', 'yes')
mock_choice.side_effect = lambda x: x[0] # Always choose the first character of the pool for predictability in tests

self.password_generator(self.jarvis, '')
self.jarvis.say.assert_called_with('Generated Password: Aa0@Aa0@Aa0@', color='green')

def test_password_length_below_minimum(self):
self.mock_inputs(8, 'yes', 'yes', 'yes', 'yes')

self.password_generator(self.jarvis, '')
self.jarvis.say.assert_called_with('Invalid length. Password length must be at least 12 characters for security.')

def test_no_character_types_selected(self):
self.mock_inputs(12, 'no', 'no', 'no', 'no')

self.password_generator(self.jarvis, '')
self.jarvis.say.assert_called_with('At least one character type must be selected!')

@patch('secrets.choice')
@patch('secrets.SystemRandom.shuffle')
def test_ensuring_diversity_in_characters(self, mock_shuffle, mock_choice):
# Using a simple deterministic pattern for secrets.choice
mock_choice.side_effect = lambda pool: pool[0] # Always select the first character
self.mock_inputs(12, 'yes', 'yes', 'yes', 'yes')

self.password_generator(self.jarvis, '')
expected_call = 'Generated Password: Aa0@Aa0@Aa0@'
self.jarvis.say.assert_called_with(expected_call, color='green')
# Check that each type of character was indeed included
self.assertTrue(all(x in expected_call for x in 'Aa0@'))

@patch('secrets.choice')
@patch('secrets.SystemRandom.shuffle')
def test_different_combinations_of_character_types(self, mock_shuffle, mock_choice):
# Combination of upper and digits only
self.mock_inputs(12, 'yes', 'no', 'yes', 'no')
mock_choice.side_effect = lambda pool: pool[0] # Simplistic choice
self.password_generator(self.jarvis, '')
expected_call = 'Generated Password: A0A0A0A0A0A0'
self.jarvis.say.assert_called_with(expected_call, color='green')
59 changes: 59 additions & 0 deletions jarviscli/tests/test_soccerApi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import unittest
from unittest.mock import patch, Mock
import requests
from your_module import SoccerScores, JarvisAPI # Adjust import according to your project structure

class TestSoccerScores(unittest.TestCase):
def setUp(self):
# Setup a mock for the JarvisAPI which would be passed to the plugin
self.jarvis = Mock()
self.jarvis.say = Mock()

# Instantiate the SoccerScores class
self.plugin = SoccerScores()

@patch('requests.get')
def test_successful_response(self, mock_get):
# Mock the JSON response to simulate the API returning valid game data
mock_get.return_value = Mock(ok=True)
mock_get.return_value.json.return_value = {
"games": [
{"strEvent": "Team A vs Team B", "dateEvent": "2024-04-16", "intHomeScore": "2", "intAwayScore": "1", "strStatus": "Finished"}
]
}
mock_get.return_value.raise_for_status = Mock()

self.plugin.print_latest_scores(self.jarvis)
self.jarvis.say.assert_any_call("Fetching the latest soccer scores...")
self.jarvis.say.assert_any_call("Match: Team A vs Team B", color=Fore.BLUE)
self.jarvis.say.assert_any_call("Score: 2 - 1", color=Fore.GREEN)

@patch('requests.get')
def test_no_games_found(self, mock_get):
# Simulate the API returning valid response but no games
mock_get.return_value = Mock(ok=True)
mock_get.return_value.json.return_value = {"games": []}
mock_get.return_value.raise_for_status = Mock()

self.plugin.print_latest_scores(self.jarvis)
self.jarvis.say.assert_called_with("No recent games data found.", color=Fore.RED)

@patch('requests.get')
def test_api_error(self, mock_get):
# Simulate an API error
mock_get.side_effect = requests.exceptions.RequestException("API Error")

self.plugin.print_latest_scores(self.jarvis)
self.jarvis.say.assert_called_with("An error occurred while fetching data: API Error", color=Fore.RED)

@patch('requests.get')
def test_invalid_json_response(self, mock_get):
# Simulate invalid JSON response
mock_get.return_value = Mock(ok=True)
mock_get.return_value.json.side_effect = ValueError("Invalid JSON")

self.plugin.print_latest_scores(self.jarvis)
self.jarvis.say.assert_called_with("An error occurred while fetching data: Invalid JSON", color=Fore.RED)

if __name__ == '__main__':
unittest.main()