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

Rspec #173

Merged
merged 4 commits into from
Nov 28, 2023
Merged

Rspec #173

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
7 changes: 7 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ Lint/MixedRegexpCaptureTypes:
Bundler/OrderedGems:
Enabled: false

RSpec/MultipleExpectations:
Enabled: false
RSpec/ExampleLength:
Enabled: false
RSpec/InstanceVariable:
Enabled: false

# Relaxed.Ruby.Style
## Version 2.5

Expand Down
2 changes: 1 addition & 1 deletion app/controllers/networks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def index
# GET /networks/:id
def show
authorize network
ips = IpLease.includes(:item).in_network(self.network)
ips = IpLease.includes(:item).find_in_network(self.network)

render inertia: "Networks/Show", props: {
network: -> { network.render(view: :show, options: {
Expand Down
33 changes: 29 additions & 4 deletions app/frontend/Pages/Networks/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
Submit,
} from '@/Components/Form'
import { type UseFormProps } from 'use-inertia-form'
import { IPAddress } from '@/lib'

window.IPAddress = IPAddress

type TNetworkFormData = {
network: Schema.NetworksFormData
Expand All @@ -29,8 +32,30 @@ const emptyNetwork: Schema.NetworksFormData = {
}

const NetworkForm = ({ to, method = 'post', onSubmit, network = emptyNetwork }: INetworkFormProps) => {
const handleAddressBlur = (value: string, form: UseFormProps<TNetworkFormData>) => {
let ip: IPAddress | undefined = undefined

form.clearErrors('network.address')

try {
ip = new IPAddress(value)

if(ip.address.subnetMask === 32) {
form.setError('network.address', `${form.getData('network.address')} is not a valid network address. Must not be a /32 address.`)

ip = undefined
}
} catch(e) {
form.setError('network.address', `${form.getData('network.address')} is not a valid network address. Value must contain subnet prefix, e.g. "192.168.1.0/24"`)
}

if(ip !== undefined && form.getData('network.gateway') === "") {
form.setData('network.gateway', ip.address.startAddressExclusive().address)
}
}

return (
<Form
<Form<TNetworkFormData>
model="network"
data={ { network } }
to={ to }
Expand All @@ -39,16 +64,16 @@ const NetworkForm = ({ to, method = 'post', onSubmit, network = emptyNetwork }:
>
<TextInput name="name" label="Name" required autoFocus />

<TextInput name="vlan_id" label="VLAN ID" />

<TextInput name="address" label="Network Address" placeholder="e.g. 192.168.1.0/24" required />
<TextInput name="address" label="Network" placeholder="e.g. 192.168.1.0/24" required onBlur={ handleAddressBlur } />

<TextInput name="gateway" label="Gateway Address" placeholder="e.g. 192.168.1.1" />

<TextInput name="dhcp_start" label="DHCP Start" placeholder="e.g. 192.168.1.100" />

<TextInput name="dhcp_end" label="DHCP End" placeholder="e.g. 192.168.1.254" />

<TextInput name="vlan_id" label="VLAN ID" />

<Textarea name="notes" label="Notes" />

<Submit>
Expand Down
1 change: 0 additions & 1 deletion app/frontend/Pages/Networks/Show/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { createContext } from '@/lib/hooks'
import { Routes } from '@/lib'
import NetworkDetailsTable from './NetworkDetailsTable'
import { EditIcon } from '@/Components/Icons'
import { Schema } from 'tabler-icons-react'

interface IShowNetworkProps {
network: Schema.NetworksShow
Expand Down
8 changes: 4 additions & 4 deletions app/models/asset.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ class Asset < ApplicationRecord

monetize :cost_cents, allow_blank: true, allow_nil: true, numericality: { greater_than_or_equal_to: 0 }

validates_presence_of :name
validates :name, presence: true

belongs_to :vendor, required: false
belongs_to :default_location, class_name: "Location", required: false
belongs_to :vendor, optional: true
belongs_to :default_location, class_name: "Location", optional: true
belongs_to :model
has_one :category, through: :model
has_one :manufacturer, through: :model
has_one :warranty, required: false
has_one :warranty, required: false, dependent: :destroy

scope :includes_associated, -> { includes([:category, :model, :assignments, :default_location, :department, :vendor, :manufacturer, :status_label, :activities, :documentations]) }

Expand Down
15 changes: 6 additions & 9 deletions app/models/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,21 @@ class Assignment < ApplicationRecord

attr_reader :assignable_types, :assign_toable_types

enum status: %i(approved requested denied)
enum status: { :approved => 0, :requested => 1, :denied => 2 }

attribute :assigned_at, default: Time.current
attribute :assigned_at, default: -> { Time.current }
attribute :active, default: true

belongs_to :assignable, polymorphic: true
belongs_to :assign_toable, polymorphic: true
belongs_to :created_by, class_name: "Person", required: false
belongs_to :created_by, class_name: "Person", optional: true
belongs_to :location

validates :assignable_type, inclusion: { in: self.assignable_types }
validates :assign_toable_type, inclusion: { in: self.assign_toable_types }
validates_presence_of :assignable_type
validates_presence_of :assignable_id
validates_presence_of :assign_toable_type
validates_presence_of :assign_toable_id
validates_presence_of :assigned_at
validates_presence_of :location_id
validates :assignable_type, presence: true
validates :assign_toable_type, presence: true
validates :assigned_at, presence: true

scope :includes_associated, -> { includes([:location, :created_by, :activities]) }
scope :active, -> { where(active: true) }
Expand Down
8 changes: 4 additions & 4 deletions app/models/category.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ class Category < ApplicationRecord

@categorizable_types = %w(Asset Item Accessory Address Component Consumable Contact Contract Department Documentation Email License Location Model Order Person Phone Ticket User Vendor Website)

validates_inclusion_of :categorizable_type, in: @categorizable_types, allow_nil: false
validates_presence_of :categorizable_type
validates_presence_of :name
validates :categorizable_type, inclusion: { in: @categorizable_types, allow_nil: false }
validates :categorizable_type, presence: true
validates :name, presence: true

scope :find_by_type, ->(type){ where(categorizable_type: type.to_s.singularize.camelize) }

Expand All @@ -38,7 +38,7 @@ def slug_from_category_type
end

def records
self.type.find_by_category(self)
self.type.find_by_category(self) # rubocop:disable Rails/DynamicFindBy
end

def qty
Expand Down
8 changes: 4 additions & 4 deletions app/models/company.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ class Company < ApplicationRecord
tracked
resourcify

attribute :default_currency, default: MoneyRails.default_currency
attribute :default_currency, default: -> { MoneyRails.default_currency }

has_many :users, through: :roles, class_name: :User, source: :users
has_one :ldap, dependent: :destroy

# Reverse polymorphic relationships. Allows searching related models through Ownable interface
# Company.items, Company.contracts, etc.
has_many :ownerships
has_many :ownerships, dependent: :restrict_with_error
{
assets: "Asset",
models: "Model",
Expand Down Expand Up @@ -63,12 +63,12 @@ class Company < ApplicationRecord
consumables: "Consumable",
components: "Component",
}.each_pair do |assoc, model|
has_many assoc, ->{ where(type: model) }, through: :ownerships, source: :ownable, source_type: :Asset, class_name: model
has_many assoc, ->{ where(type: model) }, through: :ownerships, source: :ownable, source_type: :Asset, class_name: model.to_s
end

scope :includes_associated, -> { includes([:departments, :locations, :ownerships, :documentations]) }

validates_presence_of :name
validates :name, presence: true

private

Expand Down
2 changes: 1 addition & 1 deletion app/models/contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Contract < ApplicationRecord
tracked
resourcify

validates_presence_of :name
validates :name, presence: true

belongs_to :vendor

Expand Down
4 changes: 2 additions & 2 deletions app/models/department.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ class Department < ApplicationRecord
tracked
resourcify

validates_presence_of :name
validates :name, presence: true

belongs_to :location, optional: true # primary location, such as main office of department
belongs_to :manager, class_name: :Person, optional: true

# Reverse polymorphic relationships. Allows searching related models through Ownable interface
# Department.items, Department.contracts, etc.
has_many :ownerships
has_many :ownerships, dependent: :restrict_with_error
{
assets: 'Asset',
items: 'Item',
Expand Down
4 changes: 2 additions & 2 deletions app/models/documentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ class Documentation < ApplicationRecord
resourcify

belongs_to :documentable, polymorphic: true
belongs_to :created_by, class_name: "Person", required: false
belongs_to :created_by, class_name: "Person", optional: true

validates_presence_of :title
validates :title, presence: true

scope :includes_associated, -> { includes([:created_by]) }
end
12 changes: 9 additions & 3 deletions app/models/ip_lease.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ class IpLease < ApplicationRecord
tracked
resourcify

belongs_to :nic
belongs_to :nic, inverse_of: :ips
has_one :item, through: :nic

scope :active, -> { where(active: true) }

def self.in_network(network)
# Finds IP leases within the specified network.
#
# @param [Network, IPAddress, String] network The network to search within.
# @return [ActiveRecord::Relation] The matching IP leases.
# @raise [CustomNetworkError] If the network parameter is invalid.
def self.find_in_network(network)
net_str = case network
when Network
network.address.to_string
when IPAddress
network.to_string
when String
raise ArgumentError, "Invalid parameter, #{network} is not a valid network. When passing a string, use the format \"10.10.10.0/24\"" unless IPAddress(network).network?
error_msg = "Invalid parameter, #{network} is not a valid network. When passing a string, use the format \"10.10.10.0/24\""
raise ArgumentError, error_msg unless IPAddress(network).network?

network
else
Expand Down
2 changes: 1 addition & 1 deletion app/models/item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Item < Asset
tracked
resourcify

validates_presence_of :model
validates :model, presence: true

has_many :nics, dependent: :destroy
has_many :ips, -> { where(active: true) }, through: :nics, source: :ip_leases
Expand Down
8 changes: 4 additions & 4 deletions app/models/ldap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ class Ldap < ApplicationRecord

attribute :port, default: 389

validates_presence_of :name
validates_presence_of :host
validates_presence_of :port
validates_presence_of :username
validates :name, presence: true
validates :host, presence: true
validates :port, presence: true
validates :username, presence: true

belongs_to :company
end
2 changes: 1 addition & 1 deletion app/models/license.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class License < ApplicationRecord
belongs_to :vendor
belongs_to :manufacturer

validates_presence_of :name
validates :name, presence: true

alias_attribute :seats, :qty

Expand Down
6 changes: 3 additions & 3 deletions app/models/location.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ class Location < ApplicationRecord
tracked
resourcify

belongs_to :parent, class_name: "Location", required: false
has_many :people
belongs_to :parent, class_name: "Location", optional: true
has_many :people, dependent: :nullify

validates_presence_of :name
validates :name, presence: true

scope :includes_associated, -> { includes([:parent, :department, :activities, :documentations]) }

Expand Down
2 changes: 1 addition & 1 deletion app/models/manufacturer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Manufacturer < ApplicationRecord

validates :name, presence: true, uniqueness: true

has_many :models
has_many :models, dependent: :nullify
has_many :items, through: :models
has_many :accessories, through: :models
has_many :consumables, through: :models
Expand Down
10 changes: 5 additions & 5 deletions app/models/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ class Model < ApplicationRecord
resourcify

belongs_to :manufacturer
has_many :items, -> { includes_associated }
has_many :accessories, -> { includes_associated }
has_many :consumables, -> { includes_associated }
has_many :components, -> { includes_associated }
has_many :items, -> { includes_associated }, dependent: :nullify
has_many :accessories, -> { includes_associated }, dependent: :nullify
has_many :consumables, -> { includes_associated }, dependent: :nullify
has_many :components, -> { includes_associated }, dependent: :nullify

validates_presence_of :name
validates :name, presence: true
validates :name, uniqueness: { scope: :model_number, message: "Model already exists" }

scope :includes_associated, -> { includes([:manufacturer, :category, :items, :accessories, :consumables, :components, :documentations]) }
Expand Down
18 changes: 16 additions & 2 deletions app/models/network.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,32 @@ class Network < ApplicationRecord

validates :address, presence: true
validate :network?
validate :gateway_within_network?

before_validation :normalize_network_address

private

def network?
def normalize_network_address
return unless self.address

if self.address&.prefix != 32 && !self.address&.network?
self.address = self.address.find_adjacent_subnet
end
end

def network?
unless self.address&.network?
errors.add(:address, "Must be a valid network with nothing to the right of the network bits")
errors.add(:address, "Must be a valid network")
end
end

def gateway_within_network?
return false unless self.address&.network?
return true if self.gateway.nil?

unless self.address.include? self.gateway
errors.add(:gateway, "Gateway address must be within the network")
end
end
end
8 changes: 4 additions & 4 deletions app/models/nic.rb
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
class Nic < ApplicationRecord
enum nic_type: %i(ethernet wifi fiber cellular)
enum nic_type: {:ethernet => 0, :wifi => 1, :fiber => 2, :cellular => 3}

attribute :nic_type, default: :wifi

tracked
resourcify

validates_presence_of :nic_type
validates :nic_type, presence: true
# validates_presence_of :item

belongs_to :item, class_name: "Asset"
has_many :ips, ->{ where active: true }, class_name: "IpLease"
has_many :ip_leases
has_many :ips, ->{ where active: true }, class_name: "IpLease", inverse_of: :nic, dependent: :destroy
has_many :ip_leases, inverse_of: :nic, dependent: :destroy

accepts_nested_attributes_for :ips
end
Loading