Repository: uncscode/particula

Issue:
Expand testing of species
suggestion (testing): Add edge case tests for GasSpecies

Consider adding tests for edge cases, such as creating a GasSpecies with extreme values (very large or very small) for molar mass or concentration. Also, test the behavior when invalid inputs are provided.

def test_gas_species_builder_single_species():
    """Test building a single gas species with the GasSpeciesBuilder."""
    vapor_pressure_strategy = ConstantVaporPressureStrategy(vapor_pressure=101325)
    name = "Oxygen"
    molar_mass = 0.032  # kg/mol
    condensable = False
    concentration = 1.2  # kg/m^3

    gas_species = GasSpecies(
        name=name,
        molar_mass=molar_mass,
        vapor_pressure_strategy=vapor_pressure_strategy,
        condensable=condensable,
        concentration=concentration
    )

    assert gas_species.name == name
    assert gas_species.get_molar_mass() == molar_mass
    assert gas_species.get_condensable() is condensable
    assert gas_species.get_concentration() == concentration
    assert gas_species.get_pure_vapor_pressure(298) == 101325

def test_gas_species_builder_edge_cases():
    """Test edge cases for GasSpecies creation."""
    with pytest.raises(ValueError):
        GasSpecies(name="Invalid", molar_mass=-1, vapor_pressure_strategy=None, condensable=True, concentration=0)

    large_mass_species = GasSpecies(name="Heavy", molar_mass=1e10, vapor_pressure_strategy=None, condensable=False, concentration=1e-10)
    assert large_mass_species.get_molar_mass() == 1e10
    assert large_mass_species.get_concentration() == 1e-10

Discussion:
sourcery-ai[bot]: <!-- Generated by sourcery-ai[bot]: start review_guide -->

🧙 Sourcery is reviewing your pull request!

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time. You can also use
  this command to specify where the summary should be inserted.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
github-actions[bot]: [PR Preview Action](https://github.com/rossjrw/pr-preview-action) v1.4.8
:---:
:rocket: Deployed preview to https://uncscode.github.io/particula/pr-preview/pr-529/
on branch [`gh-pages`](https://github.com/uncscode/particula/tree/gh-pages) at 2024-12-03 02:04 UTC
<!-- Sticky Pull Request Commentpr-preview -->
Gorkowski: @sourcery-ai re-review
sourcery-ai[bot]: I'm sorry, I don't understand the command `@sourcery-ai re-review`.

Please use `@sourcery-ai review` to request a review.
Gorkowski: @sourcery-ai review
Gorkowski: @mahf708 , ok ready now 

Patch:
diff --git a/particula/gas/species.py b/particula/gas/species.py
index 379dee125..d377487a4 100644
--- a/particula/gas/species.py
+++ b/particula/gas/species.py
@@ -5,6 +5,8 @@ Units are in kg/mol for molar mass, Kelvin for temperature, Pascals for
 pressure, and kg/m^3 for concentration.
 """
 
+import logging
+import warnings
 from typing import Union
 from numpy.typing import NDArray
 import numpy as np
@@ -13,37 +15,24 @@ from particula.gas.vapor_pressure_strategies import (
     ConstantVaporPressureStrategy,
 )
 
+logger = logging.getLogger("particula")
+
 
 class GasSpecies:
     """GasSpecies represents an individual or array of gas species with
     properties like name, molar mass, vapor pressure, and condensability.
 
     Attributes:
-    ------------
-    - name (str): The name of the gas species.
-    - molar_mass (float): The molar mass of the gas species.
-    - pure_vapor_pressure_strategy (VaporPressureStrategy): The strategy for
-        calculating the pure vapor pressure of the gas species. Can be a single
-        strategy or a list of strategies. Default is a constant vapor pressure
-        strategy with a vapor pressure of 0.0 Pa.
-    - condensable (bool): Indicates whether the gas species is condensable.
-        Default is True.
-    - concentration (float): The concentration of the gas species in the
-        mixture. Default is 0.0 kg/m^3.
-
-    Methods:
-    --------
-    - get_molar_mass: Get the molar mass of the gas species.
-    - get_condensable: Check if the gas species is condensable.
-    - get_concentration: Get the concentration of the gas species in the
-        mixture.
-    - get_pure_vapor_pressure: Calculate the pure vapor pressure of the gas
-        species at a given temperature.
-    - get_partial_pressure: Calculate the partial pressure of the gas species.
-    - get_saturation_ratio: Calculate the saturation ratio of the gas species.
-    - get_saturation_concentration: Calculate the saturation concentration of
-        the gas species.
-    - add_concentration: Add concentration to the gas species.
+        - name : The name of the gas species.
+        - molar_mass : The molar mass of the gas species.
+        - pure_vapor_pressure_strategy : The strategy for calculating the pure
+            vapor pressure of the gas species. Can be a single strategy or a
+            list of strategies. Default is a constant vapor pressure strategy
+            with a vapor pressure of 0.0 Pa.
+        - condensable : Indicates whether the gas species is condensable.
+            Default is True.
+        - concentration : The concentration of the gas species in the mixture.
+            Default is 0.0 kg/m^3.
     """
 
     def __init__(  # pylint: disable=too-many-positional-arguments
@@ -57,11 +46,14 @@ class GasSpecies:
         condensable: Union[bool, NDArray[np.bool_]] = True,
         concentration: Union[float, NDArray[np.float64]] = 0.0,
     ) -> None:
-        self.name = name
+        self._check_non_positive_value(molar_mass, "molar mass")
         self.molar_mass = molar_mass
+        concentration = self._check_if_negative_concentration(concentration)
+        self.concentration = concentration
+
+        self.name = name
         self.pure_vapor_pressure_strategy = vapor_pressure_strategy
         self.condensable = condensable
-        self.concentration = concentration
 
     def __str__(self):
         """Return a string representation of the GasSpecies object."""
@@ -79,31 +71,31 @@ class GasSpecies:
         """Get the name of the gas species.
 
         Returns:
-        - name (str or NDArray[np.str_]): The name of the gas species."""
+            The name of the gas species.
+        """
         return self.name
 
     def get_molar_mass(self) -> Union[float, NDArray[np.float64]]:
         """Get the molar mass of the gas species in kg/mol.
 
         Returns:
-        - molar_mass (float or NDArray[np.float64]): The molar mass of the gas
-            species, in kg/mol."""
+            The molar mass of the gas species, in kg/mol.
+        """
         return self.molar_mass
 
     def get_condensable(self) -> Union[bool, NDArray[np.bool_]]:
         """Check if the gas species is condensable or not.
 
         Returns:
-        - condensable (bool): True if the gas species is condensable, False
-            otherwise."""
+            True if the gas species is condensable, False otherwise.
+        """
         return self.condensable
 
     def get_concentration(self) -> Union[float, NDArray[np.float64]]:
         """Get the concentration of the gas species in the mixture, in kg/m^3.
 
         Returns:
-        - concentration (float or NDArray[np.float64]): The concentration of
-            the gas species in the mixture.
+            The concentration of the gas species in the mixture.
         """
         return self.concentration
 
@@ -117,15 +109,19 @@ class GasSpecies:
         for calculating vapor pressure.
 
         Args:
-        - temperature (float or NDArray[np.float64]): The temperature in
-        Kelvin at which to calculate vapor pressure.
+            - temperature : The temperature in Kelvin at which to calculate
+                vapor pressure.
 
         Returns:
-        - vapor_pressure (float or NDArray[np.float64]): The calculated pure
-        vapor pressure in Pascals.
+            The calculated pure vapor pressure in Pascals.
 
         Raises:
             ValueError: If no vapor pressure strategy is set.
+
+        Example:
+            ``` py title="Example usage of get_pure_vapor_pressure"
+            gas_object.get_pure_vapor_pressure(temperature=298)
+            ```
         """
         if isinstance(self.pure_vapor_pressure_strategy, list):
             # Handle a list of strategies: calculate and return a list of vapor
@@ -146,22 +142,27 @@ class GasSpecies:
     def get_partial_pressure(
         self, temperature: Union[float, NDArray[np.float64]]
     ) -> Union[float, NDArray[np.float64]]:
-        """
-        Calculate the partial pressure of the gas based on the vapor
-        pressure strategy. This method accounts for multiple strategies if
-        assigned and calculates partial pressure for each strategy based on
-        the corresponding concentration and molar mass.
+        """Calculate the partial pressure of the gas based on the vapor
+        pressure strategy.
 
-        Parameters:
-        - temperature (float or NDArray[np.float64]): The temperature in
-        Kelvin at which to calculate the partial pressure.
+        This method accounts for multiple strategies if assigned and
+        calculates partial pressure for each strategy based on the
+        corresponding concentration and molar mass.
+
+        Args:
+            - temperature : The temperature in Kelvin at which to calculate
+                the partial pressure.
 
         Returns:
-        - partial_pressure (float or NDArray[np.float64]): Partial pressure
-        of the gas in Pascals.
+            Partial pressure of the gas in Pascals.
 
         Raises:
-        - ValueError: If the vapor pressure strategy is not set.
+            ValueError: If the vapor pressure strategy is not set.
+
+        Example:
+            ``` py title="Example usage of get_partial_pressure"
+            gas_object.get_partial_pressure(temperature=298)
+            ```
         """
         # Handle multiple vapor pressure strategies
         if isinstance(self.pure_vapor_pressure_strategy, list):
@@ -189,22 +190,27 @@ class GasSpecies:
     def get_saturation_ratio(
         self, temperature: Union[float, NDArray[np.float64]]
     ) -> Union[float, NDArray[np.float64]]:
-        """
-        Calculate the saturation ratio of the gas based on the vapor
-        pressure strategy. This method accounts for multiple strategies if
-        assigned and calculates saturation ratio for each strategy based on
-        the corresponding concentration and molar mass.
+        """Calculate the saturation ratio of the gas based on the vapor
+        pressure strategy.
 
-        Parameters:
-        - temperature (float or NDArray[np.float64]): The temperature in
-        Kelvin at which to calculate the partial pressure.
+        This method accounts for multiple strategies if assigned and
+        calculates saturation ratio for each strategy based on the
+        corresponding concentration and molar mass.
+
+        Args:
+            - temperature : The temperature in Kelvin at which to calculate
+                the partial pressure.
 
         Returns:
-        - saturation_ratio (float or NDArray[np.float64]): The saturation ratio
-        of the gas
+            The saturation ratio of the gas.
 
         Raises:
-        - ValueError: If the vapor pressure strategy is not set.
+            ValueError : If the vapor pressure strategy is not set.
+
+        Example:
+            ``` py title="Example usage of get_saturation_ratio"
+            gas_object.get_saturation_ratio(temperature=298)
+            ```
         """
         # Handle multiple vapor pressure strategies
         if isinstance(self.pure_vapor_pressure_strategy, list):
@@ -232,26 +238,31 @@ class GasSpecies:
     def get_saturation_concentration(
         self, temperature: Union[float, NDArray[np.float64]]
     ) -> Union[float, NDArray[np.float64]]:
-        """
-        Calculate the saturation concentration of the gas based on the vapor
-        pressure strategy. This method accounts for multiple strategies if
-        assigned and calculates saturation concentration for each strategy
-        based on the molar mass.
+        """Calculate the saturation concentration of the gas based on the
+        vapor pressure strategy.
 
-        Parameters:
-        - temperature (float or NDArray[np.float64]): The temperature in
-        Kelvin at which to calculate the partial pressure.
+        This method accounts for multiple strategies if assigned and
+        calculates saturation concentration for each strategy based on the
+        molar mass.
+
+        Args:
+            - temperature : The temperature in Kelvin at which to calculate
+                the partial pressure.
 
         Returns:
-        - saturation_concentration (float or NDArray[np.float64]): The
-        saturation concentration of the gas
+            The saturation concentration of the gas.
 
         Raises:
-        - ValueError: If the vapor pressure strategy is not set.
+            ValueError: If the vapor pressure strategy is not set.
+
+        Example:
+            ``` py title="Example usage of get_saturation_concentration"
+            gas_object.get_saturation_concentration(temperature=298)
+            ```
         """
         # Handle multiple vapor pressure strategies
         if isinstance(self.pure_vapor_pressure_strategy, list):
-            # Calculate saturation concentraiton for each strategy
+            # Calculate saturation concentration for each strategy
             return np.array(
                 [
                     strategy.saturation_concentration(
@@ -271,11 +282,54 @@ class GasSpecies:
 
     def add_concentration(
         self, added_concentration: Union[float, NDArray[np.float64]]
-    ):
+    ) -> None:
         """Add concentration to the gas species.
 
         Args:
-        - added_concentration (float): The concentration to add to the gas
-            species."""
-        self.concentration = self.concentration + added_concentration
-        self.concentration = np.maximum(self.concentration, 0.0)
+            - added_concentration : The concentration to add to the gas
+                species.
+
+        Example:
+            ``` py title="Example usage of add_concentration"
+            gas_object.add_concentration(added_concentration=1e-10)
+            ```
+        """
+        self.set_concentration(self.concentration + added_concentration)
+
+    def set_concentration(
+        self, new_concentration: Union[float, NDArray[np.float64]]
+    ) -> None:
+        """Set the concentration of the gas species.
+
+        Args:
+            - new_concentration : The new concentration of the gas species.
+
+        Example:
+            ``` py title="Example usage of set_concentration"
+            gas_object.set_concentration(new_concentration=1e-10)
+            ```
+        """
+        new_concentration = self._check_if_negative_concentration(
+            new_concentration)
+        self.concentration = new_concentration
+
+    def _check_if_negative_concentration(
+        self, values: Union[float, NDArray[np.float64]]
+    ) -> Union[float, NDArray[np.float64]]:
+        """Log a warning if the concentration is negative."""
+        if np.any(values < 0.0):
+            message = "Negative concentration in gas species, set = 0."
+            logger.warning(message)
+            warnings.warn(message, UserWarning)
+            # Set negative concentrations to 0
+            values = np.maximum(values, 0.0)
+        return values
+
+    def _check_non_positive_value(
+        self, value: Union[float, NDArray[np.float64]], name: str
+    ) -> None:
+        """Check for non-positive values and raise an error if found."""
+        if np.any(value <= 0.0):
+            message = f"Non-positive {name} in gas species, stopping."
+            logger.error(message)
+            raise ValueError(message)
diff --git a/particula/gas/species_builders.py b/particula/gas/species_builders.py
index 70554b34e..5982a1e0b 100644
--- a/particula/gas/species_builders.py
+++ b/particula/gas/species_builders.py
@@ -31,29 +31,18 @@ class GasSpeciesBuilder(
     readable creation of GasSpecies instances with optional parameters.
 
     Attributes:
-        name: The name of the gas species.
-        molar_mass: The molar mass of the gas species in kg/mol.
-        vapor_pressure_strategy: The vapor pressure strategy for the
+        - name : The name of the gas species.
+        - molar_mass : The molar mass of the gas species in kg/mol.
+        - vapor_pressure_strategy : The vapor pressure strategy for the
             gas species.
-        condensable: Whether the gas species is condensable.
-        concentration: The concentration of the gas species in the
+        - condensable : Whether the gas species is condensable.
+        - concentration : The concentration of the gas species in the
             mixture, in kg/m^3.
 
-    Methods:
-        set_name: Set the name of the gas species.
-        set_molar_mass: Set the molar mass of the gas species in kg/mol.
-        set_vapor_pressure_strategy: Set the vapor pressure strategy
-            for the gas species.
-        set_condensable: Set the condensable bool of the gas species.
-        set_concentration: Set the concentration of the gas species in the
-            mixture, in kg/m^3.
-        set_parameters: Set the parameters of the GasSpecies object from
-            a dictionary including optional units.
-
     Raises:
-        ValueError: If any required key is missing. During check_keys and
+        - ValueError : If any required key is missing. During check_keys and
             pre_build_check. Or if trying to set an invalid parameter.
-        Warning: If using default units for any parameter.
+        - Warning : If using default units for any parameter.
     """
 
     def __init__(self):
@@ -72,7 +61,20 @@ class GasSpeciesBuilder(
         self.condensable = None
 
     def set_name(self, name: Union[str, NDArray[np.str_]]):
-        """Set the name of the gas species."""
+        """Set the name of the gas species.
+
+        Args:
+            name : The name of the gas species.
+
+        Returns:
+            self : The GasSpeciesBuilder object.
+
+        Examples:
+            ``` py title="Set a single name"
+            builder = GasSpeciesBuilder()
+            builder.set_name("Oxygen")
+            ```
+        """
         self.name = name
         return self
 
@@ -80,7 +82,22 @@ class GasSpeciesBuilder(
         self,
         strategy: Union[VaporPressureStrategy, list[VaporPressureStrategy]],
     ):
-        """Set the vapor pressure strategy for the gas species."""
+        """Set the vapor pressure strategy for the gas species.
+
+        Args:
+            strategy : The vapor pressure strategy for the gas species.
+
+        Returns:
+            self : The GasSpeciesBuilder object.
+
+        Examples:
+            ``` py title="Set a single vapor pressure strategy"
+            builder = GasSpeciesBuilder()
+            builder.set_vapor_pressure_strategy(
+                ConstantVaporPressureStrategy(vapor_pressure=1.0)
+            )
+            ```
+        """
         self.vapor_pressure_strategy = strategy
         return self
 
@@ -88,12 +105,24 @@ class GasSpeciesBuilder(
         self,
         condensable: Union[bool, NDArray[np.bool_]],
     ):
-        """Set the condensable bool of the gas species."""
+        """Set the condensable bool of the gas species.
+
+        Args:
+            condensable : Whether the gas species is condensable.
+
+        Returns:
+            self : The GasSpeciesBuilder object.
+
+        Examples:
+            ``` py title="Set a single condensable bool"
+            builder = GasSpeciesBuilder()
+            builder.set_condensable(False)
+            ```
+        """
         self.condensable = condensable
         return self
 
     def build(self) -> GasSpecies:
-        """Validate and return the GasSpecies object."""
         self.pre_build_check()
         return GasSpecies(
             name=self.name,  # type: ignore
@@ -111,7 +140,6 @@ class PresetGasSpeciesBuilder(
 ):
     """Builder class for GasSpecies objects, allowing for a more fluent and
     readable creation of GasSpecies instances with optional parameters.
-
     """
 
     def __init__(self):
@@ -125,7 +153,6 @@ class PresetGasSpeciesBuilder(
         self.concentration = 1.0
 
     def build(self) -> GasSpecies:
-
         self.pre_build_check()
         return GasSpecies(
             name=self.name,  # type: ignore
diff --git a/particula/gas/species_factories.py b/particula/gas/species_factories.py
index 385caec4b..1d7965bcd 100644
--- a/particula/gas/species_factories.py
+++ b/particula/gas/species_factories.py
@@ -23,20 +23,40 @@ class GasSpeciesFactory(
     Factory class to create species builders for creating gas species.
 
     Methods:
-        get_builders: Returns the mapping of strategy types to builder
+        - get_builders : Returns the mapping of strategy types to builder
         instances.
-        get_strategy: Gets the strategy instance
-        for the specified strategy type.
-            strategy_type: Type of species builder to use, can be
-            'gas_species' or 'preset_gas_species'.
-            parameters: Parameters required for the
-            builder, dependent on the chosen strategy type.
+        - get_strategy : Gets the strategy instance
+            - strategy_type : Type of species builder to use, can be
+                'gas_species' or 'preset_gas_species'.
+            - parameters : Parameters required for the
+                builder, dependent on the chosen strategy type.
 
     Returns:
-        GasSpecies: An instance of the specified GasSpecies.
+        GasSpecies : An instance of the specified GasSpecies.
 
     Raises:
-        ValueError: If an unknown strategy type is provided.
+        ValueError : If an unknown strategy type is provided.
+
+    Example:
+        ``` py title="Create a preset gas species using the factory"
+        factory = GasSpeciesFactory()
+        gas_object = factory.get_strategy("preset_gas_species", parameters)
+        ```
+
+        ``` py title="Create a gas species using the factory"
+        factory = GasSpeciesFactory()
+        parameters = {
+            "name": "Oxygen",
+            "molar_mass": 0.032,
+            "vapor_pressure_strategy": ConstantVaporPressureStrategy(
+                vapor_pressure=101325
+            ),
+            "condensable": False,
+            "concentration": 1.2,
+        }
+        gas_object = factory.get_strategy("gas_species", parameters)
+        ```
+
     """
 
     def get_builders(self):
@@ -44,8 +64,8 @@ class GasSpeciesFactory(
 
         Returns:
             A dictionary mapping strategy types to builder instances.
-                - gas_species: GasSpeciesBuilder
-                - preset_gas_species: PresetGasSpeciesBuilder
+                - gas_species : GasSpeciesBuilder
+                - preset_gas_species : PresetGasSpeciesBuilder
         """
         return {
             "gas_species": GasSpeciesBuilder(),


Test patch:
diff --git a/particula/gas/tests/species_factory_test.py b/particula/gas/tests/species_factory_test.py
new file mode 100644
index 000000000..433215588
--- /dev/null
+++ b/particula/gas/tests/species_factory_test.py
@@ -0,0 +1,68 @@
+"""Test the species factory module, does build the gas species object."""
+
+import unittest
+from particula.gas.species_factories import GasSpeciesFactory
+from particula.gas.species_builders import (
+    GasSpeciesBuilder,
+    PresetGasSpeciesBuilder,
+)
+from particula.gas.species import GasSpecies
+from particula.gas.vapor_pressure_strategies import (
+    ConstantVaporPressureStrategy,
+)
+
+
+class TestGasSpeciesFactory(unittest.TestCase):
+    """Test the GasSpeciesFactory class."""
+    def setUp(self):
+        """Set up the test case."""
+        self.factory = GasSpeciesFactory()
+
+    def test_get_builders(self):
+        """Test getting the builders."""
+        builders = self.factory.get_builders()
+        self.assertIsInstance(builders["gas_species"], GasSpeciesBuilder)
+        self.assertIsInstance(
+            builders["preset_gas_species"], PresetGasSpeciesBuilder
+        )
+
+    def test_get_strategy_gas_species(self):
+        """Test getting a gas species strategy."""
+        parameters = {
+            "name": "Oxygen",
+            "molar_mass": 0.032,
+            "vapor_pressure_strategy": ConstantVaporPressureStrategy(50),
+            "condensable": False,
+            "concentration": 1.2,
+        }
+        gas_species = self.factory.get_strategy("gas_species", parameters)
+        self.assertIsInstance(gas_species, GasSpecies)
+        self.assertEqual(gas_species.name, parameters["name"])
+        self.assertEqual(gas_species.molar_mass, parameters["molar_mass"])
+        self.assertEqual(
+            gas_species.pure_vapor_pressure_strategy,
+            parameters["vapor_pressure_strategy"],
+        )
+        self.assertEqual(gas_species.condensable, parameters["condensable"])
+        self.assertEqual(gas_species.concentration, parameters["concentration"])
+        self.assertIsInstance(gas_species, GasSpecies)
+
+    def test_get_strategy_preset_gas_species(self):
+        """Test getting a preset gas species strategy."""
+        parameters = {}
+        preset_gas_species = self.factory.get_strategy(
+            "preset_gas_species", parameters
+        )
+        self.assertIsInstance(preset_gas_species, GasSpecies)
+        self.assertEqual(preset_gas_species.molar_mass, 0.1)
+        self.assertIsInstance(
+            preset_gas_species.pure_vapor_pressure_strategy,
+            ConstantVaporPressureStrategy,
+        )
+        self.assertEqual(preset_gas_species.condensable, False)
+        self.assertEqual(preset_gas_species.concentration, 1.0)
+
+    def test_get_strategy_invalid(self):
+        """Test getting an invalid strategy."""
+        with self.assertRaises(ValueError):
+            self.factory.get_strategy("invalid_type", {})
diff --git a/particula/gas/tests/species_test.py b/particula/gas/tests/species_test.py
index 32e1c0005..6de9b3e7f 100644
--- a/particula/gas/tests/species_test.py
+++ b/particula/gas/tests/species_test.py
@@ -3,6 +3,7 @@
 # pylint: disable=##R0801
 
 import numpy as np
+import pytest
 from particula.gas.species import GasSpecies
 from particula.gas.vapor_pressure_strategies import (
     ConstantVaporPressureStrategy,
@@ -64,3 +65,153 @@ def test_gas_species_builder_array_species():
         gas_species.get_pure_vapor_pressure(np.array([298, 300])),
         np.array([101325, 101325]),
     )
+
+
+def test_gas_species_negative_molar_mass():
+    """Test gas species with negative molar mass."""
+    with pytest.raises(ValueError):
+        GasSpecies(
+            name="NegativeMass",
+            molar_mass=-1,
+            condensable=False,
+            concentration=1e-10,
+        )
+
+    # array input
+    with pytest.raises(ValueError):
+        GasSpecies(
+            name="NegativeMass",
+            molar_mass=np.array([-1, 1]),
+            condensable=False,
+            concentration=np.array([1e-10, 1e-10]),
+        )
+
+
+def test_gas_species_zero_molar_mass():
+    """Test gas species with zero molar mass."""
+    with pytest.raises(ValueError):
+        GasSpecies(
+            name="ZeroMass",
+            molar_mass=0,
+            condensable=False,
+            concentration=1e-10,
+        )
+
+    # array input
+    with pytest.raises(ValueError):
+        GasSpecies(
+            name="ZeroMass",
+            molar_mass=np.array([0, 1]),
+            condensable=False,
+            concentration=np.array([1e-10, 1e-10]),
+        )
+
+
+def test_gas_species_negative_concentration():
+    """Test gas species with negative concentration."""
+    with pytest.warns(UserWarning):
+        gas_species = GasSpecies(
+            name="NegativeConcentration",
+            molar_mass=1e10,
+            condensable=False,
+            concentration=-1,
+        )
+        assert gas_species.get_concentration() == 0.0
+
+    # array input
+    with pytest.warns(UserWarning):
+        gas_species = GasSpecies(
+            name="NegativeConcentration",
+            molar_mass=np.array([1, 1]),
+            condensable=False,
+            concentration=np.array([-1, 1]),
+        )
+        assert np.array_equal(gas_species.get_concentration(), [0.0, 1.0])
+
+
+def test_gas_species_zero_concentration():
+    """Test gas species with zero concentration."""
+    zero_concentration_species = GasSpecies(
+        name="ZeroConcentration",
+        molar_mass=1e10,
+        condensable=False,
+        concentration=0,
+    )
+
+    assert zero_concentration_species.get_molar_mass() == 1e10
+    assert zero_concentration_species.get_concentration() == 0
+
+    # array input
+    zero_concentration_species = GasSpecies(
+        name="ZeroConcentration",
+        molar_mass=np.array([1, 1]),
+        condensable=False,
+        concentration=np.array([0, 1]),
+    )
+
+    assert np.array_equal(zero_concentration_species.get_molar_mass(), [1, 1])
+    assert np.array_equal(
+        zero_concentration_species.get_concentration(), [0, 1]
+    )
+
+
+def test_gas_species_get_methods():
+    """Test the get methods of GasSpecies."""
+    gas_species = GasSpecies(
+        name="TestSpecies",
+        molar_mass=0.044,
+        vapor_pressure_strategy=ConstantVaporPressureStrategy(1000),
+        condensable=True,
+        concentration=0.5,
+    )
+    assert gas_species.get_name() == "TestSpecies"
+    assert gas_species.get_molar_mass() == 0.044
+    assert gas_species.get_condensable() is True
+    assert gas_species.get_concentration() == 0.5
+
+
+def test_gas_species_set_concentration():
+    """Test the set_concentration method of GasSpecies."""
+    gas_species = GasSpecies(
+        name="TestSpecies",
+        molar_mass=0.044,
+        vapor_pressure_strategy=ConstantVaporPressureStrategy(1000),
+        condensable=True,
+        concentration=0.5,
+    )
+    gas_species.set_concentration(1.0)
+    assert gas_species.get_concentration() == 1.0
+    with pytest.warns(UserWarning):
+        gas_species.set_concentration(-1.0)
+    assert gas_species.get_concentration() == 0.0
+
+
+def test_gas_species_add_concentration():
+    """Test the add_concentration method of GasSpecies."""
+    gas_species = GasSpecies(
+        name="TestSpecies",
+        molar_mass=0.044,
+        vapor_pressure_strategy=ConstantVaporPressureStrategy(1000),
+        condensable=True,
+        concentration=0.5,
+    )
+    gas_species.add_concentration(0.5)
+    assert gas_species.get_concentration() == 1.0
+    with pytest.warns(UserWarning):
+        gas_species.add_concentration(-3.0)
+    assert gas_species.get_concentration() == 0.0
+
+
+def test_gas_species_array_operations():
+    """Test array operations for concentration in GasSpecies."""
+    gas_species = GasSpecies(
+        name="TestSpecies",
+        molar_mass=0.044,
+        vapor_pressure_strategy=ConstantVaporPressureStrategy(1000),
+        condensable=True,
+        concentration=0.5,
+    )
+    gas_species.set_concentration(np.array([0.5, 1.0]))
+    assert np.array_equal(gas_species.get_concentration(), [0.5, 1.0])
+    gas_species.add_concentration(np.array([0.5, -1.0]))
+    assert np.array_equal(gas_species.get_concentration(), [1.0, 0.0])