diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..375de9191aea --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +.pytest_cache diff --git a/AUTHORS b/AUTHORS index 6fb53b5277cf..2fe09e1086bc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,3 +2,5 @@ Ezra Nobrega Justin Majetich +Abel Mulugeta +Newton Sigei diff --git a/commit.git b/commit.git new file mode 100755 index 000000000000..7879998409c1 --- /dev/null +++ b/commit.git @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ $# -eq 0 ]; then + echo "Error: No commit message provided." + echo "Usage: $0 " + exit 1 +fi + +git add . +git commit -m "$*" +git push diff --git a/console.py b/console.py index 13a8af68e930..979e1e280917 100755 --- a/console.py +++ b/console.py @@ -2,38 +2,46 @@ """ Console Module """ import cmd import sys -from models.base_model import BaseModel + from models.__init__ import storage -from models.user import User -from models.place import Place -from models.state import State -from models.city import City from models.amenity import Amenity +from models.base_model import BaseModel +from models.city import City +from models.place import Place from models.review import Review +from models.state import State +from models.user import User class HBNBCommand(cmd.Cmd): - """ Contains the functionality for the HBNB console""" + """Contains the functionality for the HBNB console""" # determines prompt for interactive/non-interactive modes - prompt = '(hbnb) ' if sys.__stdin__.isatty() else '' + prompt = "(hbnb) " if sys.__stdin__.isatty() else "" classes = { - 'BaseModel': BaseModel, 'User': User, 'Place': Place, - 'State': State, 'City': City, 'Amenity': Amenity, - 'Review': Review - } - dot_cmds = ['all', 'count', 'show', 'destroy', 'update'] + "BaseModel": BaseModel, + "User": User, + "Place": Place, + "State": State, + "City": City, + "Amenity": Amenity, + "Review": Review, + } + dot_cmds = ["all", "count", "show", "destroy", "update"] types = { - 'number_rooms': int, 'number_bathrooms': int, - 'max_guest': int, 'price_by_night': int, - 'latitude': float, 'longitude': float - } + "number_rooms": int, + "number_bathrooms": int, + "max_guest": int, + "price_by_night": int, + "latitude": float, + "longitude": float, + } def preloop(self): """Prints if isatty is false""" if not sys.__stdin__.isatty(): - print('(hbnb)') + print("(hbnb)") def precmd(self, line): """Reformat command line for advanced command syntax. @@ -41,31 +49,31 @@ def precmd(self, line): Usage: .([ [<*args> or <**kwargs>]]) (Brackets denote optional fields in usage example.) """ - _cmd = _cls = _id = _args = '' # initialize line elements + _cmd = _cls = _id = _args = "" # initialize line elements # scan for general formating - i.e '.', '(', ')' - if not ('.' in line and '(' in line and ')' in line): + if not ("." in line and "(" in line and ")" in line): return line try: # parse line left to right pline = line[:] # parsed line # isolate - _cls = pline[:pline.find('.')] + _cls = pline[: pline.find(".")] # isolate and validate - _cmd = pline[pline.find('.') + 1:pline.find('(')] + _cmd = pline[pline.find(".") + 1:pline.find("(")] if _cmd not in HBNBCommand.dot_cmds: raise Exception # if parantheses contain arguments, parse them - pline = pline[pline.find('(') + 1:pline.find(')')] + pline = pline[pline.find("(") + 1:pline.find(")")] if pline: # partition args: (, [], [<*args>]) - pline = pline.partition(', ') # pline convert to tuple + pline = pline.partition(", ") # pline convert to tuple # isolate _id, stripping quotes - _id = pline[0].replace('\"', '') + _id = pline[0].replace('"', "") # possible bug here: # empty quotes register as empty _id when replaced @@ -73,13 +81,16 @@ def precmd(self, line): pline = pline[2].strip() # pline is now str if pline: # check for *args or **kwargs - if pline[0] is '{' and pline[-1] is'}'\ - and type(eval(pline)) is dict: + if ( + pline[0] is "{" + and pline[-1] is "}" + and type(eval(pline)) is dict + ): _args = pline else: - _args = pline.replace(',', '') + _args = pline.replace(",", "") # _args = _args.replace('\"', '') - line = ' '.join([_cmd, _cls, _id, _args]) + line = " ".join([_cmd, _cls, _id, _args]) except Exception as mess: pass @@ -89,57 +100,102 @@ def precmd(self, line): def postcmd(self, stop, line): """Prints if isatty is false""" if not sys.__stdin__.isatty(): - print('(hbnb) ', end='') + print("(hbnb) ", end="") return stop def do_quit(self, command): - """ Method to exit the HBNB console""" + """Method to exit the HBNB console""" exit() def help_quit(self): - """ Prints the help documentation for quit """ + """Prints the help documentation for quit""" print("Exits the program with formatting\n") def do_EOF(self, arg): - """ Handles EOF to exit program """ + """Handles EOF to exit program""" print() exit() def help_EOF(self): - """ Prints the help documentation for EOF """ + """Prints the help documentation for EOF""" print("Exits the program without formatting\n") def emptyline(self): - """ Overrides the emptyline method of CMD """ + """Overrides the emptyline method of CMD""" pass def do_create(self, args): - """ Create an object of any class""" + """Create an object of any class with optional attributes.""" + args = args.split() if not args: print("** class name missing **") return - elif args not in HBNBCommand.classes: + + class_name = args[0] + if class_name not in HBNBCommand.classes: print("** class doesn't exist **") return - new_instance = HBNBCommand.classes[args]() - storage.save() + + # Create an instance of the specified class + new_instance = HBNBCommand.classes[class_name]() + + # Parse attributes from the remaining arguments + for param in args[1:]: + if "=" not in param: + continue + + key, value = param.split("=", 1) + + # Handle string values + if value.startswith('"') and value.endswith('"'): + # Extract the inner value between quotes + temp_value = value[1:-1] + + # skips if value contain a space + if ' ' in temp_value: + continue + + # Handle escaped quotes and underscores + temp_value = temp_value.replace("_", " ").replace('\\"', '"') + + # skips if value contains a " whithout the scape character '\' + if '"' in temp_value: + continue + + # Assign temp_value back to value + value = temp_value + elif "." in value: # Handle floats + try: + value = float(value) + except ValueError: + continue + else: # Handle integers + try: + value = int(value) + except ValueError: + continue + + # Set the attribute to the new instance + setattr(new_instance, key, value) + + # Save the new instance + new_instance.save() print(new_instance.id) - storage.save() def help_create(self): - """ Help information for the create method """ + """Help information for the create method""" print("Creates a class of any type") print("[Usage]: create \n") def do_show(self, args): - """ Method to show an individual object """ + """Method to show an individual object""" new = args.partition(" ") c_name = new[0] c_id = new[2] # guard against trailing args - if c_id and ' ' in c_id: - c_id = c_id.partition(' ')[0] + if c_id and " " in c_id: + c_id = c_id.partition(" ")[0] if not c_name: print("** class name missing **") @@ -160,17 +216,17 @@ def do_show(self, args): print("** no instance found **") def help_show(self): - """ Help information for the show command """ + """Help information for the show command""" print("Shows an individual instance of a class") print("[Usage]: show \n") def do_destroy(self, args): - """ Destroys a specified object """ + """Destroys a specified object""" new = args.partition(" ") c_name = new[0] c_id = new[2] - if c_id and ' ' in c_id: - c_id = c_id.partition(' ')[0] + if c_id and " " in c_id: + c_id = c_id.partition(" ")[0] if not c_name: print("** class name missing **") @@ -187,36 +243,35 @@ def do_destroy(self, args): key = c_name + "." + c_id try: - del(storage.all()[key]) + del storage.all()[key] storage.save() except KeyError: print("** no instance found **") def help_destroy(self): - """ Help information for the destroy command """ + """Help information for the destroy command""" print("Destroys an individual instance of a class") print("[Usage]: destroy \n") def do_all(self, args): - """ Shows all objects, or all objects of a class""" + """Shows all objects, or all objects of a class""" print_list = [] if args: - args = args.split(' ')[0] # remove possible trailing args + args = args.split(" ")[0] # remove possible trailing args if args not in HBNBCommand.classes: print("** class doesn't exist **") return - for k, v in storage._FileStorage__objects.items(): - if k.split('.')[0] == args: - print_list.append(str(v)) + objects = storage.all(HBNBCommand.classes[args]) else: - for k, v in storage._FileStorage__objects.items(): - print_list.append(str(v)) + objects = storage.all() + for obj in objects.values(): + print_list.append(str(obj)) print(print_list) def help_all(self): - """ Help information for the all command """ + """Help information for the all command""" print("Shows all objects, or all of a class") print("[Usage]: all \n") @@ -224,7 +279,7 @@ def do_count(self, args): """Count current number of class instances""" count = 0 for k, v in storage._FileStorage__objects.items(): - if args == k.split('.')[0]: + if args == k.split(".")[0]: count += 1 print(count) @@ -233,8 +288,8 @@ def help_count(self): print("Usage: count ") def do_update(self, args): - """ Updates a certain object with new info """ - c_name = c_id = att_name = att_val = kwargs = '' + """Updates a certain object with new info""" + c_name = c_id = att_name = att_val = kwargs = "" # isolate cls from id/args, ex: (, delim, ) args = args.partition(" ") @@ -264,7 +319,7 @@ def do_update(self, args): return # first determine if kwargs or args - if '{' in args[2] and '}' in args[2] and type(eval(args[2])) is dict: + if "{" in args[2] and "}" in args[2] and type(eval(args[2])) is dict: kwargs = eval(args[2]) args = [] # reformat kwargs into list, ex: [, , ...] for k, v in kwargs.items(): @@ -272,23 +327,23 @@ def do_update(self, args): args.append(v) else: # isolate args args = args[2] - if args and args[0] is '\"': # check for quoted arg - second_quote = args.find('\"', 1) + if args and args[0] is '"': # check for quoted arg + second_quote = args.find('"', 1) att_name = args[1:second_quote] args = args[second_quote + 1:] - args = args.partition(' ') + args = args.partition(" ") # if att_name was not quoted arg - if not att_name and args[0] is not ' ': + if not att_name and args[0] is not " ": att_name = args[0] # check for quoted val arg - if args[2] and args[2][0] is '\"': - att_val = args[2][1:args[2].find('\"', 1)] + if args[2] and args[2][0] is '"': + att_val = args[2][1:args[2].find('"', 1)] # if att_val was not quoted arg if not att_val and args[2]: - att_val = args[2].partition(' ')[0] + att_val = args[2].partition(" ")[0] args = [att_name, att_val] @@ -298,7 +353,7 @@ def do_update(self, args): # iterate through attr names and values for i, att_name in enumerate(args): # block only runs on even iterations - if (i % 2 == 0): + if i % 2 == 0: att_val = args[i + 1] # following item is value if not att_name: # check for att_name print("** attribute name missing **") @@ -316,9 +371,10 @@ def do_update(self, args): new_dict.save() # save updates to file def help_update(self): - """ Help information for the update class """ + """Help information for the update class""" print("Updates an object with new information") print("Usage: update \n") + if __name__ == "__main__": HBNBCommand().cmdloop() diff --git a/file.json b/file.json new file mode 100644 index 000000000000..23d4db925cdb --- /dev/null +++ b/file.json @@ -0,0 +1 @@ +{"State.527e6a49-c014-4eb3-a8f5-552828db52a9": {"id": "527e6a49-c014-4eb3-a8f5-552828db52a9", "created_at": "2025-01-03T08:14:21.025044", "updated_at": "2025-01-03T08:14:21.025089", "name": "California", "__class__": "State"}, "State.e6de2029-70e1-40d0-92fc-5b98f178985a": {"id": "e6de2029-70e1-40d0-92fc-5b98f178985a", "created_at": "2025-01-03T08:22:03.988407", "updated_at": "2025-01-03T08:22:03.988450", "name": "California", "__class__": "State"}, "State.8c9eaf64-c8a4-4732-9bb1-9953fd3aa470": {"id": "8c9eaf64-c8a4-4732-9bb1-9953fd3aa470", "created_at": "2025-01-03T08:22:03.992004", "updated_at": "2025-01-03T08:22:03.992034", "name": "Arizona", "__class__": "State"}, "Place.9ff91e09-d5d9-49cc-9a80-ea4ad671e1d2": {"id": "9ff91e09-d5d9-49cc-9a80-ea4ad671e1d2", "created_at": "2025-01-03T08:22:04.003335", "updated_at": "2025-01-03T08:22:04.003380", "city_id": "0001", "user_id": "0001", "name": "My little house", "number_rooms": 4, "number_bathrooms": 2, "max_guest": 10, "price_by_night": 300, "latitude": 37.773972, "longitude": -122.431297, "__class__": "Place"}} \ No newline at end of file diff --git a/main_delete.py b/main_delete.py new file mode 100755 index 000000000000..75083e1bae12 --- /dev/null +++ b/main_delete.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +""" Test delete feature +""" +from models.engine.file_storage import FileStorage +from models.state import State + +fs = FileStorage() + +# All States +all_states = fs.all(State) +print("All States: {}".format(len(all_states.keys()))) +for state_key in all_states.keys(): + print(all_states[state_key]) + +# Create a new State +new_state = State() +new_state.name = "California" +fs.new(new_state) +fs.save() +print("New State: {}".format(new_state)) + +# All States +all_states = fs.all(State) +print("All States: {}".format(len(all_states.keys()))) +for state_key in all_states.keys(): + print(all_states[state_key]) + +# Create another State +another_state = State() +another_state.name = "Nevada" +fs.new(another_state) +fs.save() +print("Another State: {}".format(another_state)) + +# All States +all_states = fs.all(State) +print("All States: {}".format(len(all_states.keys()))) +for state_key in all_states.keys(): + print(all_states[state_key]) +# Delete the new State +fs.delete(new_state) + +# All States +all_states = fs.all(State) +print("All States: {}".format(len(all_states.keys()))) +for state_key in all_states.keys(): + print(all_states[state_key]) diff --git a/models/__init__.py b/models/__init__.py index d3765c2bc603..24790b5ec59e 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,7 +1,14 @@ #!/usr/bin/python3 """This module instantiates an object of class FileStorage""" from models.engine.file_storage import FileStorage +from os import getenv +if getenv('HBNB_TYPE_STORAGE') == 'db': + from models.engine.db_storage import DBStorage + storage = DBStorage() + storage.reload() +else: + from models.engine.file_storage import FileStorage + storage = FileStorage() + storage.reload() -storage = FileStorage() -storage.reload() diff --git a/models/base_model.py b/models/base_model.py index 4856e9de421f..2dd39147d110 100644 --- a/models/base_model.py +++ b/models/base_model.py @@ -2,10 +2,16 @@ """This module defines a base class for all models in our hbnb clone""" import uuid from datetime import datetime +from sqlalchemy import Column,String, DateTime +from sqlalchemy.ext.declarative import declarative_base +Base = declarative_base() class BaseModel: """A base class for all hbnb models""" + id = Column(String(60), unique=True, nullable=False, primary_key=True) + created_at = Column(DateTime, nullable=False, default=datetime.utcnow()) + updated_at = Column(DateTime, nullable=False, default=datetime.utcnow) def __init__(self, *args, **kwargs): """Instatntiates a new model""" if not kwargs: @@ -13,13 +19,15 @@ def __init__(self, *args, **kwargs): self.id = str(uuid.uuid4()) self.created_at = datetime.now() self.updated_at = datetime.now() - storage.new(self) else: kwargs['updated_at'] = datetime.strptime(kwargs['updated_at'], '%Y-%m-%dT%H:%M:%S.%f') kwargs['created_at'] = datetime.strptime(kwargs['created_at'], '%Y-%m-%dT%H:%M:%S.%f') del kwargs['__class__'] + for key, value in kwargs.items(): + if not hasattr(self, key): + setattr(self, key, value) self.__dict__.update(kwargs) def __str__(self): @@ -31,6 +39,7 @@ def save(self): """Updates updated_at with current time when instance is changed""" from models import storage self.updated_at = datetime.now() + storage.new(self) storage.save() def to_dict(self): @@ -41,4 +50,9 @@ def to_dict(self): (str(type(self)).split('.')[-1]).split('\'')[0]}) dictionary['created_at'] = self.created_at.isoformat() dictionary['updated_at'] = self.updated_at.isoformat() + if '_sa_instance_state' in dictionary: + del dictionary['_sa_instance_state'] return dictionary + def delete(self): + from models import storage + storage.delete(self) diff --git a/models/city.py b/models/city.py index b9b4fe221502..908c9d44a42f 100644 --- a/models/city.py +++ b/models/city.py @@ -1,9 +1,12 @@ #!/usr/bin/python3 """ City Module for HBNB project """ -from models.base_model import BaseModel +from models.base_model import BaseModel, Base +from sqlalchemy import Column, String, ForeignKey +from sqlalchemy.orm import relationship - -class City(BaseModel): +class City(BaseModel, Base): """ The city class, contains state ID and name """ - state_id = "" - name = "" + __tablename__ = 'cities' + state_id = Column(String(60), ForeignKey('states.id'), nullable=False, ) + name = Column(String(128), nullable=False) + state = relationship("State", backref="cities", cascade="all, delete") diff --git a/models/engine/db_storage.py b/models/engine/db_storage.py new file mode 100644 index 000000000000..1ff275cd1f5c --- /dev/null +++ b/models/engine/db_storage.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 +"""This module defines a new engine""" +from os import getenv +from sqlalchemy import create_engine + +class DBStorage: + """DBStorage engine""" + __engine = None + __session = None + + def __init__(self): + """Constructor""" + user = getenv('HBNB_MYSQL_USER') + password = getenv('HBNB_MYSQL_PASSWORD') + host = getenv('HBNB_MYSQL_HOST') + database = getenv('HBNB_MYSQL_DB') + self.__engine = create_engine(f'mysql+mysqldb://{user}:{password}@{host}/{database}', pool_pre_ping=True) + if getenv('HBNB_ENV') == 'test': + from models.base_model import Base + Base.metadata.drop_all(self.__engine) + + def all(self, cls=None): + """ + Query all objects of a given class from the database session. + If cls is None, query all objects from all classes. + """ + from models.base_model import Base + from models.state import State + from models.city import City + from models.user import User + from models.amenity import Amenity + from models.place import Place + from models.review import Review + result = {} + if cls: + # Query all objects of the given class + objects = self.__session.query(cls).all() + for obj in objects: + key = f"{obj.__class__.__name__}.{obj.id}" + result[key] = obj + else: + classes = [User, State, City, Amenity, Place, Review] + for classes in classes: + objects = self.__session.query(classes).all() + for obj in objects: + key = f"{obj.__class__.__name__}.{obj.id}" + result[key] = obj + return result + + def new(self, obj): + """add the object to the current database session""" + self.__session.add(obj) + def save(self): + """commit all changes of the current database session""" + self.__session.commit() + + def delete(self, obj=None): + """delete the object from the current database session""" + if obj is not None: + self.__session.delete(obj) + + def reload(self): + """reload the database""" + from models.base_model import Base + from models.state import State + from models.city import City + from sqlalchemy.orm import sessionmaker, scoped_session + + Base.metadata.create_all(self.__engine) + + session_factory = sessionmaker(bind=self.__engine, expire_on_commit=True) + self.__session = scoped_session(session_factory) +if __name__ == "__main__": + all("State") \ No newline at end of file diff --git a/models/engine/file_storage.py b/models/engine/file_storage.py index 6f5d7f8d4680..b364cf183ee1 100644 --- a/models/engine/file_storage.py +++ b/models/engine/file_storage.py @@ -8,8 +8,16 @@ class FileStorage: __file_path = 'file.json' __objects = {} - def all(self): - """Returns a dictionary of models currently in storage""" + def all(self, cls=None): + """ + Returns a dictionary of models currently in storage. + If `cls` is provided, filters and returns objects of that class type. + """ + if cls: + return { + key: obj for key, obj in FileStorage.__objects.items() + if isinstance(obj, cls) + } return FileStorage.__objects def new(self, obj): @@ -45,6 +53,16 @@ def reload(self): with open(FileStorage.__file_path, 'r') as f: temp = json.load(f) for key, val in temp.items(): - self.all()[key] = classes[val['__class__']](**val) + self.all()[key] = classes[val['__class__']](**val) except FileNotFoundError: pass + + def delete(self, obj=None): + """ + Deletes obj from __objects if it exists. + If obj is None, does nothing. + """ + if obj: + key = f"{obj.__class__.__name__}.{obj.id}" + if key in FileStorage.__objects: + del FileStorage.__objects[key] diff --git a/models/state.py b/models/state.py index 583f041f07e4..233640d0c63a 100644 --- a/models/state.py +++ b/models/state.py @@ -1,8 +1,18 @@ #!/usr/bin/python3 """ State Module for HBNB project """ -from models.base_model import BaseModel +from sqlalchemy.orm import relationship +from models.base_model import BaseModel, Base +from models import storage +from sqlalchemy import Column, String - -class State(BaseModel): +class State(BaseModel, Base): """ State class """ - name = "" + __tablename__ = 'states' + name = Column(String(128), nullable=False) + cities = relationship('City', back_populates='states', cascade="all, delete") + @property + def cities(self): + from models.city import City + all_cities = storage.all() + return [city for city in all_cities.values() if city.state_id == self.id] + diff --git a/setup_mysql_dev.sql b/setup_mysql_dev.sql new file mode 100644 index 000000000000..5a3ec16d92be --- /dev/null +++ b/setup_mysql_dev.sql @@ -0,0 +1,7 @@ +-- preparing MySQL server +CREATE DATABASE IF NOT EXISTS hbnb_dev_db; +CREATE USER IF NOT EXISTS 'hbnb_dev'@'localhost' IDENTIFIED BY 'hbnb_dev_pwd'; +GRANT ALL PRIVILEGES ON hbnb_dev_db.* TO 'hbnb_dev'@'localhost'; +FLUSH PRIVILEGES; +GRANT SELECT ON performance_schema.* TO 'hbnb_dev'@'localhost'; +FLUSH PRIVILEGES; diff --git a/setup_mysql_test.sql b/setup_mysql_test.sql new file mode 100644 index 000000000000..d964cbe5ba83 --- /dev/null +++ b/setup_mysql_test.sql @@ -0,0 +1,7 @@ +-- preparing MySQL server - test +CREATE DATABASE IF NOT EXISTS hbnb_test_db; +CREATE USER IF NOT EXISTS 'hbnb_test'@'localhost' IDENTIFIED BY 'hbnb_test_pwd'; +GRANT ALL PRIVILEGES ON hbnb_test_db.* TO 'hbnb_test'@'localhost'; +FLUSH PRIVILEGES; +GRANT SELECT ON performance_schema.* TO 'hbnb_test'@'localhost'; +FLUSH PRIVILEGES; diff --git a/test_params_create b/test_params_create new file mode 100644 index 000000000000..503857d4ba5d --- /dev/null +++ b/test_params_create @@ -0,0 +1,6 @@ +create State name="California" +create State name="Arizona" +all State + +create Place city_id="0001" user_id="0001" name="My_little_house" number_rooms=4 number_bathrooms=2 max_guest=10 price_by_night=300 latitude=37.773972 longitude=-122.431297 +all Place diff --git a/tests/test_models/test_base_model.py b/tests/test_models/test_base_model.py index b6fef535c595..3f023d55d73b 100755 --- a/tests/test_models/test_base_model.py +++ b/tests/test_models/test_base_model.py @@ -21,12 +21,6 @@ def setUp(self): """ """ pass - def tearDown(self): - try: - os.remove('file.json') - except: - pass - def test_default(self): """ """ i = self.value() diff --git a/tests/test_models/test_console.py b/tests/test_models/test_console.py new file mode 100755 index 000000000000..6062da7fa863 --- /dev/null +++ b/tests/test_models/test_console.py @@ -0,0 +1,98 @@ +#!/usr/bin/python3 +"""Unit tests for the HBNB command interpreter.""" + +import unittest +from io import StringIO +from unittest.mock import patch + +from console import HBNBCommand +from models import storage +from models.amenity import Amenity +from models.base_model import BaseModel +from models.city import City +from models.place import Place +from models.review import Review +from models.state import State +from models.user import User + + +class TestHBNBCommand(unittest.TestCase): + """Test cases for the HBNB command interpreter.""" + + def setUp(self): + """Set up test cases.""" + self.console = HBNBCommand() + self.classes = { + "BaseModel": BaseModel, + "User": User, + "State": State, + "City": City, + "Amenity": Amenity, + "Place": Place, + "Review": Review, + } + + def tearDown(self): + """Clean up after each test.""" + storage.all().clear() + storage.save() + + def test_create(self): + """Test create command with and without parameters.""" + # Test create without class name + with patch("sys.stdout", new=StringIO()) as f: + self.console.onecmd("create") + self.assertEqual("** class name missing **", f.getvalue().strip()) + + # Test create with invalid class name + with patch("sys.stdout", new=StringIO()) as f: + self.console.onecmd("create InvalidClass") + self.assertEqual("** class doesn't exist **", f.getvalue().strip()) + + # Test create with valid class names without parameters + for class_name in self.classes: + with patch("sys.stdout", new=StringIO()) as f: + self.console.onecmd(f"create {class_name}") + obj_id = f.getvalue().strip() + self.assertTrue(len(obj_id) > 0) + key = f"{class_name}.{obj_id}" + self.assertIn(key, storage.all()) + + # Test create with valid parameters + with patch("sys.stdout", new=StringIO()) as f: + self.console.onecmd('create Place name="My_little_house" number_rooms=4 latitude=37.773972 longitude=-122.431297') + obj_id = f.getvalue().strip() + self.assertTrue(len(obj_id) > 0) + key = f"Place.{obj_id}" + self.assertIn(key, storage.all()) + obj = storage.all()[key] + self.assertEqual(obj.name, "My little house") + self.assertEqual(obj.number_rooms, 4) + self.assertEqual(obj.latitude, 37.773972) + self.assertEqual(obj.longitude, -122.431297) + + # Test create with invalid parameters (should skip them) + with patch("sys.stdout", new=StringIO()) as f: + self.console.onecmd('create Place name="Invalid name with space" invalid_param="123" number_rooms=4') + obj_id = f.getvalue().strip() + self.assertTrue(len(obj_id) > 0) + key = f"Place.{obj_id}" + self.assertIn(key, storage.all()) + obj = storage.all()[key] + # "Invalid name with space" should be skipped + self.assertEqual(obj.name, '') + # "invalid_param" should be correctly set + self.assertEqual(obj.invalid_param, '123') + # "number_rooms" should be correctly set + self.assertEqual(obj.number_rooms, 4) + + # Test create with escaped quotes and underscores + with patch("sys.stdout", new=StringIO()) as f: + self.console.onecmd('create Place description="A_house_with_\"escaped\"_quotes" max_guest=10') + obj_id = f.getvalue().strip() + self.assertTrue(len(obj_id) > 0) + key = f"Place.{obj_id}" + self.assertIn(key, storage.all()) + obj = storage.all()[key] + self.assertEqual(obj.description, 'A house with "escaped" quotes') + self.assertEqual(obj.max_guest, 10) diff --git a/tests/test_models/test_engine/test_file_storage.py b/tests/test_models/test_engine/test_file_storage.py index e1de7198b697..f0de5f3670f9 100755 --- a/tests/test_models/test_engine/test_file_storage.py +++ b/tests/test_models/test_engine/test_file_storage.py @@ -1,9 +1,10 @@ #!/usr/bin/python3 """ Module for testing file storage""" +import os import unittest -from models.base_model import BaseModel + from models import storage -import os +from models.base_model import BaseModel class test_fileStorage(unittest.TestCase): @@ -21,7 +22,7 @@ def tearDown(self): """ Remove storage file at end of tests """ try: os.remove('file.json') - except: + except Exception: pass def test_obj_list_empty(self):