diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..c99d2e7 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9b5eca2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,27 @@ +# Agent Notes + +## Project Shape +- Rails 8.1 app (`CarTracker`) on Ruby `4.0.6`; `.ruby-version` includes the `ruby-` prefix, while the Dockerfile uses `ARG RUBY_VERSION=4.0.6`. +- The app is still mostly generated Rails skeleton. Treat `DESIGN.md` as product direction, not proof of implemented models/routes. +- Database is SQLite under `storage/`. Production is configured with separate SQLite databases for primary, Solid Cache, Solid Queue, and Solid Cable. +- JavaScript uses importmap + Turbo + Stimulus; there is no `package.json` or npm install step. Pin JS dependencies with `bin/importmap`, not npm/yarn. + +## Commands +- Setup/update dependencies and DB: `bin/setup --skip-server`. Omit `--skip-server` only when you want it to start `bin/dev` afterward. +- Run the app: `bin/dev` (it just execs `bin/rails server`). +- Repo CI script: `bin/ci` runs setup, RuboCop, bundler-audit, importmap audit, and strict Brakeman. It does not run specs. +- GitHub CI also runs Brakeman, bundler-audit, importmap audit, and RuboCop only; do not assume tests ran in CI. +- Style check: `bin/rubocop` (`rubocop-rails-omakase` via `.rubocop.yml`). +- Security checks: `bin/bundler-audit`, `bin/importmap audit`, `bin/brakeman --quiet --no-pager --exit-on-warn --exit-on-error`. + +## Tests +- RSpec is installed; run specs with `bundle exec rspec`, not `bin/rails test`. +- Run a focused spec with `bundle exec rspec spec/path/to/file_spec.rb:LINE`. +- `.rspec` only requires `spec_helper`; Rails specs need `require "rails_helper"` explicitly. +- `DESIGN.md` says to develop features test-first. Add specs for new behavior even though current CI does not run them. + +## Rails/Runtime Gotchas +- `config/application.rb` disables Rails system test generation and does not load `rails/test_unit/railtie`. +- `ApplicationController` rejects non-modern browsers with `allow_browser versions: :modern` and uses `stale_when_importmap_changes`. +- Solid Queue jobs can run with `bin/jobs`; Kamal production config currently runs Solid Queue inside Puma via `SOLID_QUEUE_IN_PUMA: true`. +- Production Docker is intended for Kamal/production, not local development; the entrypoint runs `bin/rails db:prepare` before starting the server. diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..bfc487d --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,153 @@ +# Vehicle Tracker Application Design + +## Goal +A web application to replace manual spreadsheet tracking of vehicle maintenance +and fuel usage of a shared fleet. + +## Architecture & Philosophy +* **Language:** Ruby on Rails +* **Style:** Snake_case +* **Principle:** Keep it simple. Do not over-optimize. CRUD is the priority. +* **Database:** Relational, tracking "Events" linked to specific "Entities." +* **Tests:** We should be test driven in our development. No feature should + be created without a test. + +## Data models + +### 1. User + +This table will contain information about the users of the app. Mostly used to +know who updated what. This table will include: + +* *Fields:* `name`, `email` + + +### 2. Vehicle + +This table will contain information about the vehicles (assets) that we +track. They have no strict ownership relationship as they can be used/driven by +multiple operators. The vehicle information will include: + +* *Fields:* `make`, `model`, `year`, `color`, `vin`, + `licence_place`, `current_odometer`, `fuel_tank_size` + +### 3. MaintenanceSchedule +Configures the maintenance rules for a specific vehicle. This allows the +"overdue" check to be automated by comparing the current schedule against the +actual history. +* *Fields:* `vehicle_id`, `maintenance_type`, `mileage_interval`, + `time_interval` +* *Relationship:* Belongs to Vehicle + +### 4. FuelEntry +Records specific refueling events. +* *Fields:* `vehicle_id`, `odometer` (at time of fill), `gallons_pumped`, + `price_paid`, `date`, `updated_by_user_id` (Tracks who submitted) +* *Relationship:* Belongs to Vehicle + +### 5. MainetnanceEntry +Records maintenance and repair history. +* *Fields:* `vehicle_id`, `odometer` (at time of service), `description` + (Should exactly match a `maintenance_type` value from the + MaintenanceSchedule), `cost`, `date`, `updated_by_user_id` (Tracks who + submitted) +* *Relationship:* Belongs to Vehicle + +## Data Relationships + +* **Vehicle** has_many **MaintenanceSchedules** +* **Vehicle** has_many **FuelEntries** +* **Vehicle** has_many **MaintenanceEntries** +* **User** has_many **FuelEntries** (as the actor) +* **User** has_many **MaintenanceEntries** (as the actor) + +## Data Flow + +### Scenario A: Fueling +1. User navigates to `Vehicle#show`. +2. User fills form with Odometer, Gallons, Price. +3. User submits -> `FuelEntry` is saved to DB. +4. `current_odometer` is updated on the Vehicle record. + +### Scenario B: Maintenance +1. User navigates to `Vehicle#show`. +2. User fills form with Description, Cost, Date. +3. User submits -> `MaintenanceEntry` is saved to DB. + +### Scenario C: Overdue Alerts +1. On `Vehicle#show`, query the most recent `MaintenanceEntry`. +2. Compare `MaintenanceEntry.date` vs `Date.today`. +3. If difference > 6 months, display alert message to User. + +Note that there is in the MaintenanceSchedule data, there is a `time_interval` +field that defines the length of time for maintenance. There is also a milage +that should be used as well and it is an or operation for them. + +## User Authentication + +Because this is an internal application, I plan on something like this: + +The server will every month generate a key. This key will be printed out in a +QR code format. The users will then scan the QR code to collect the key. When +they make a request, this key is sent along with it and is used to verify the +user. Ideally each user would use the QR code to prove that I know them, enter +a name and then get a session token that is used instead of sending the raw QR +code. The idea is that it is proof that I know who they are by them posessing +information that only I have (the QR code) and then in the future they should +use the token to prove who they are. + +Again, because this is an internal application, there should not be any +passwords. Users are not really created to be logged in, more because I need to +track who is doing what with the equipment. + +## Database choice + +I have no hard requirements. I would prefer the database to be easy to use for +development. This application will really only ever have up to 100 users so I +do not need high performance. SQLite should work fantastic in this case. + +## Test framework + +The test framework is rspec. You can run the tests with `bin/rails test` + +## Vehicle ownership + +Vehicles can be used by multiple operators. We do not need to track who is +driving though. We just want to know about who is submitting updates and +enteries. + +## Rails information + +``` +$ rails --version +Rails 8.1.3.1 +``` + +## Deployment + +This will be a local development deployment. Once we are ready, it will be +packaged into a Docker container and put on a production server. This container +is designed to run behind a reverse proxy that is already in place. + + +# Final notes + +The relationship between the user and vehicle is minimal. This is on purpose +because we really are only using the user to track who is making changes. We do +not really care if it is the actual driver or not. + +For overdue alerts, we will send notifications to the users using a +notification service. This is not yet set up so we will not worry about it. We +can leave stubs though for that to happen. The notification service just +requires a curl request so it is simple to add later. For now, just log it into +the console so that we can test it is working. + +For data access, it would be nice to have 'admin' and 'regular' users however +that is a distinction that we do not yet need. Remember to keep things simple +but flexible in the future. Do not wall us in with how code is done. + +Each edit by the users will be a seperate database entry. There should be no +user editing the same entry at any time. The only possible issue that I can see +is if we have two users in the same vehicle and they both submit a fuel +entry. In this case, we would just delete one of the entries. At no point in +time should two users be editing the same record. diff --git a/Gemfile b/Gemfile index f3e1431..de5c2e1 100644 --- a/Gemfile +++ b/Gemfile @@ -41,6 +41,11 @@ gem "thruster", require: false gem "image_processing", "~> 1.2" group :development, :test do + # Testing frameworks + gem "rspec-rails" + gem "factory_bot_rails" + gem "capybara" + # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri windows ], require: "debug/prelude" @@ -50,6 +55,11 @@ group :development, :test do # Static analysis for security vulnerabilities [https://brakemanscanner.org/] gem "brakeman", require: false + gem "rubocop" + gem "rubocop-rails" + gem "rubocop-rspec" + gem "rubocop-rspec_rails" + # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/] gem "rubocop-rails-omakase", require: false end diff --git a/Gemfile.lock b/Gemfile.lock index b7fecf7..6b0cec1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -75,6 +75,8 @@ GEM securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) uri (>= 0.13.1) + addressable (2.9.0) + public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) base64 (0.3.0) bcrypt_pbkdf (1.1.2) @@ -88,6 +90,15 @@ GEM bundler-audit (0.9.3) bundler (>= 1.2.0) thor (~> 1.0) + capybara (3.40.0) + addressable + matrix + mini_mime (>= 0.1.3) + nokogiri (~> 1.11) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) concurrent-ruby (1.3.8) connection_pool (3.0.2) crass (1.0.7) @@ -95,6 +106,7 @@ GEM debug (1.11.1) irb (~> 1.10) reline (>= 0.3.8) + diff-lcs (1.6.2) dotenv (3.2.0) drb (2.2.3) ed25519 (1.4.0) @@ -102,6 +114,11 @@ GEM erubi (1.13.1) et-orbi (1.4.2) tzinfo + factory_bot (6.6.0) + activesupport (>= 6.1.0) + factory_bot_rails (6.5.1) + factory_bot (~> 6.5) + railties (>= 6.1.0) ffi (1.17.4-aarch64-linux-gnu) ffi (1.17.4-aarch64-linux-musl) ffi (1.17.4-arm-linux-gnu) @@ -156,6 +173,7 @@ GEM net-pop net-smtp marcel (1.2.1) + matrix (0.4.3) mini_magick (5.4.0) logger mini_mime (1.1.5) @@ -203,6 +221,7 @@ GEM actionpack (>= 7.0.0) activesupport (>= 7.0.0) rack + public_suffix (7.0.5) puma (8.0.2) nio4r (~> 2.0) raabro (1.5.0) @@ -259,6 +278,23 @@ GEM regexp_parser (2.12.0) reline (0.7.0) io-console (~> 0.5) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.8) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.13.0) + rspec-rails (8.0.4) + actionpack (>= 7.2) + activesupport (>= 7.2) + railties (>= 7.2) + rspec-core (>= 3.13.0, < 5.0.0) + rspec-expectations (>= 3.13.0, < 5.0.0) + rspec-mocks (>= 3.13.0, < 5.0.0) + rspec-support (>= 3.13.0, < 5.0.0) + rspec-support (3.13.7) rubocop (1.91.0) json (>= 2.3) language_server-protocol (~> 3.17.0.2) @@ -287,6 +323,14 @@ GEM rubocop (>= 1.72) rubocop-performance (>= 1.24) rubocop-rails (>= 2.30) + rubocop-rspec (3.10.2) + lint_roller (~> 1.1) + regexp_parser (>= 2.0) + rubocop (~> 1.86, >= 1.86.2) + rubocop-rspec_rails (2.32.0) + lint_roller (~> 1.1) + rubocop (~> 1.72, >= 1.72.1) + rubocop-rspec (~> 3.5) ruby-progressbar (1.13.0) ruby-vips (2.3.0) ffi (~> 1.12) @@ -347,6 +391,8 @@ GEM base64 websocket-extensions (>= 0.1.0) websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) zeitwerk (2.8.3) PLATFORMS @@ -363,7 +409,9 @@ DEPENDENCIES bootsnap brakeman bundler-audit + capybara debug + factory_bot_rails image_processing (~> 1.2) importmap-rails jbuilder @@ -371,7 +419,12 @@ DEPENDENCIES propshaft puma (>= 5.0) rails (~> 8.1.3, >= 8.1.3.1) + rspec-rails + rubocop + rubocop-rails rubocop-rails-omakase + rubocop-rspec + rubocop-rspec_rails solid_cable solid_cache solid_queue @@ -395,6 +448,7 @@ CHECKSUMS activerecord (8.1.3.1) sha256=0a2fb6c28f4938f6b013a3a549bec0a7e37d535f3dc8990e804bcc3258c0403b activestorage (8.1.3.1) sha256=f555254f387b1cffa499d2fd3115d12635eadc5b15206a8534316a67036163ef activesupport (8.1.3.1) sha256=85458765f25ea48b9019c46b6bb3fa5683197bf4280d9f06710a6e8d7a831376 + addressable (2.9.0) sha256=7fdf6ac3660f7f4e867a0838be3f6cf722ace541dd97767fa42bc6cfa980c7af ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383 base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b bcrypt_pbkdf (1.1.2) sha256=c2414c23ce66869b3eb9f643d6a3374d8322dfb5078125c82792304c10b94cf6 @@ -404,17 +458,21 @@ CHECKSUMS brakeman (8.0.6) sha256=759cc69341115e6c2dcd47b6fd8649a0b9bd540e3585ac8a0a94e31c66fee386 builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f bundler-audit (0.9.3) sha256=81c8766c71e47d0d28a0f98c7eed028539f21a6ea3cd8f685eb6f42333c9b4e9 + capybara (3.40.0) sha256=42dba720578ea1ca65fd7a41d163dd368502c191804558f6e0f71b391054aeef concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1 connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295 date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0 debug (1.11.1) sha256=2e0b0ac6119f2207a6f8ac7d4a73ca8eb4e440f64da0a3136c30343146e952b6 + diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962 dotenv (3.2.0) sha256=e375b83121ea7ca4ce20f214740076129ab8514cd81378161f11c03853fe619d drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373 ed25519 (1.4.0) sha256=16e97f5198689a154247169f3453ef4cfd3f7a47481fde0ae33206cdfdcac506 erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92 erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9 et-orbi (1.4.2) sha256=bb555dae668419cb24caa2a293a170e58be6d4df1e017c51f5030bdc133cd20c + factory_bot (6.6.0) sha256=1fc1b3b5620ec980a6a27aec1b6ec8c250ca82962e970e8a40f93e8d388d4b89 + factory_bot_rails (6.5.1) sha256=d3cc4851eae4dea8a665ec4a4516895045e710554d2b5ac9e68b94d351bc6d68 ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39 ffi (1.17.4-arm-linux-gnu) sha256=d6dbddf7cb77bf955411af5f187a65b8cd378cb003c15c05697f5feee1cb1564 @@ -437,6 +495,7 @@ CHECKSUMS loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918 mail (2.9.1) sha256=06574eca475253d6c18145dd70af80d0eb970182d55053497c5f4d797ea160e8 marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f + matrix (0.4.3) sha256=a0d5ab7ddcc1973ff690ab361b67f359acbb16958d1dc072b8b956a286564c5b mini_magick (5.4.0) sha256=f120af581d9ed4ec52c57f35a67a605d112bb1d8f582d1415b147fda42d11d78 mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1 @@ -462,6 +521,7 @@ CHECKSUMS prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193 prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85 propshaft (1.3.2) sha256=1d56a3e56a92c21bfc29caf07406b5386b00d4c47ddf357cf989a5a234b1389e + public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623 puma (8.0.2) sha256=c8ed871dfbbe66448ea9ffd46692342d9804d4071522b52b5331b7b6e7b686fb raabro (1.5.0) sha256=3f998a7bc84f9c84df3ab580634d2e0a5bda4f0841168d56035f529c9877440a racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f @@ -479,11 +539,18 @@ CHECKSUMS rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469 regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d + rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d + rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836 + rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47 + rspec-rails (8.0.4) sha256=06235692fc0892683d3d34977e081db867434b3a24ae0dd0c6f3516bad4e22df + rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c rubocop (1.91.0) sha256=9c82b7bf391c5d7e3798c5b9996e22a1fe3bd7468e351dfdeb96140c058296d0 rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db rubocop-performance (1.27.0) sha256=eeeb1374d062a368ee1c787b70eb0b0cc4b184cb1f8565f424760946146d61ce rubocop-rails (2.37.0) sha256=6e1645add5060e0328f8ddda0d820f55697c591394398bf14bb9dccb62f14b7e rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d + rubocop-rspec (3.10.2) sha256=0b3e2ecc592cd10ecbf0095bb58d1e357905276e069643523cc19eb7495f65e2 + rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100 ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33 ruby-vips (2.3.0) sha256=e685ec02c13969912debbd98019e50492e12989282da5f37d05f5471442f5374 securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1 @@ -513,6 +580,7 @@ CHECKSUMS web-console (4.3.0) sha256=e13b71301cdfc2093f155b5aa3a622db80b4672d1f2f713119cc7ec7ac6a6da4 websocket-driver (0.8.2) sha256=97c556b019bf3410b4961002ac501621e9322d3f8a7bc02161a09301cc4c4146 websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241 + xpath (3.2.0) sha256=6dfda79d91bb3b949b947ecc5919f042ef2f399b904013eb3ef6d20dd3a4082e zeitwerk (2.8.3) sha256=2c85125a8467ce069e20123d1e709a08955c9d29c118c25b46b7b7fafdbb92e5 BUNDLED WITH diff --git a/PROGRAMMER.md b/PROGRAMMER.md new file mode 100644 index 0000000..562b6a8 --- /dev/null +++ b/PROGRAMMER.md @@ -0,0 +1,41 @@ +# About me + +I am familar in these languages: + +- Python +- Ruby +- C/C++ +- VHDL + +I have built multiple applications in these languages. Here are some examples +of what I have designed and built: + +Using Ruby on Rails: A ticket framework that integrates with Stripe to be able +to sell tickets to concerts. + +Using Flask/Django: A fiber optic network tracker that allows for clients to +update the connections in a fiber optic network based on the connections in +cases. The program is capable of auto routing new connections through boxes and +tracks who makes what changes for history of work. + +Using C/C++: Many embedded projects. A bluetooth beacon tracker bot that can +follow a bluetooth device. A project that integrates with an FPGA using UART +for communication. Uses the FPGA for real time controll and data collection +while the C/C++ code is used for navigation and decision making + + +# Rules I have + +Keep things simple. There is no need to overcomplicate the project that we are +working on. Yes we should be aware of future growth however "premature +optimization is the root of all evil" We should not be careless in how we +create and start building however we do not need to plan for every eventuality. + + +# Personal preferences + +I use Emacs for my editor. I strongly prefer open source toolchains. Personally +I prefer snake_case unless the language convention specifies otherwise. My +favorite language is Ruby. + + diff --git a/README.md b/README.md index 7db80e4..ba13c9e 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,17 @@ -# README +# Car Tracker -This README would normally document whatever steps are necessary to get the -application up and running. +Vehicle maintenance and fuel tracking application. -Things you may want to cover: +## Testing -* Ruby version +Run the application test suite with: -* System dependencies +```sh +bundle exec rspec +``` -* Configuration +Run the full local CI baseline with: -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... +```sh +bin/ci +``` diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 0000000..95f2992 --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,4 @@ +class HomeController < ApplicationController + def index + end +end diff --git a/app/views/home/index.html.erb b/app/views/home/index.html.erb new file mode 100644 index 0000000..f1e10ba --- /dev/null +++ b/app/views/home/index.html.erb @@ -0,0 +1,4 @@ +
+

Car Tracker

+

Vehicle maintenance and fuel tracking will be added here.

+
diff --git a/config/ci.rb b/config/ci.rb index 239b343..86134a1 100644 --- a/config/ci.rb +++ b/config/ci.rb @@ -3,6 +3,8 @@ CI.run do step "Setup", "bin/setup --skip-server" + step "Test: RSpec", "bundle exec rspec" + step "Style: Ruby", "bin/rubocop" step "Security: Gem audit", "bin/bundler-audit" diff --git a/config/routes.rb b/config/routes.rb index 48254e8..079b0cc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,6 +9,5 @@ Rails.application.routes.draw do # get "manifest" => "rails/pwa#manifest", as: :pwa_manifest # get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker - # Defines the root path route ("/") - # root "posts#index" + root "home#index" end diff --git a/executions/phase001.md b/executions/phase001.md new file mode 100644 index 0000000..ef4fb6c --- /dev/null +++ b/executions/phase001.md @@ -0,0 +1,36 @@ +# Phase 001 Execution + +## Scope +- Prepared the Rails/RSpec project baseline before adding application models. +- Added a minimal visible root page for the application. +- Documented the project test command. + +## Changes +- Confirmed `rspec-rails` and `factory_bot_rails` are present in the Gemfile. +- Added `capybara` for system spec support. +- Enabled RSpec spec type inference from file location. +- Included FactoryBot syntax methods in RSpec configuration. +- Configured system specs to use Capybara's `rack_test` driver by default. +- Added `HomeController#index` as a placeholder landing page. +- Set the root route to `home#index`. +- Added a minimal root page view. +- Added a request spec for `GET /`. +- Added a system spec for the placeholder landing page. +- Added `bundle exec rspec` to the CI sequence. +- Updated `README.md` with the RSpec command and local CI command. + +## Decisions +- FactoryBot is the preferred future test data setup because it is already in the Gemfile. +- Capybara is required because `rspec-rails` system specs depend on it for browser-style interactions. +- `rack_test` is enough for the baseline placeholder system spec and avoids requiring Selenium before JavaScript browser coverage is needed. +- No model specs were added because there are no real application models yet. +- The root page is intentionally plain because Phase 001 only requires a visible landing point. +- The documented test command is `bundle exec rspec`, matching project agent notes. + +## Verification +- Initial `bundle exec rspec` failed because `capybara` was not in the bundle. +- After adding `capybara`, `bundle exec rspec` failed because the default system spec driver expected `selenium-webdriver`. +- Configured system specs to use `rack_test`, then reran verification. +- Passed: `bundle exec rspec` +- Passed: `bin/rubocop` +- Passed: `bin/ci` diff --git a/planning/phase001 b/planning/phase001 new file mode 100644 index 0000000..524e7f6 --- /dev/null +++ b/planning/phase001 @@ -0,0 +1,21 @@ +# Phase 001: Project Baseline And Testing Setup + +## Goal +Establish a stable Rails/RSpec foundation before adding application behavior. + +## Work +- Confirm `rspec-rails` is installed and usable. +- Ensure `bin/rails spec` or the project-preferred test command works. +- Add initial request/system/model spec patterns for future phases. +- Decide whether the app should use Rails fixtures or FactoryBot; prefer FactoryBot because it is already in the Gemfile. +- Add a simple root page placeholder so the app has a visible landing point. + +## Deliverables +- Working test suite. +- Root route and minimal home page. +- Documented test command. + +## Acceptance Criteria +- Test suite passes. +- Root path returns success. +- No application models are added without corresponding specs. diff --git a/planning/phase002 b/planning/phase002 new file mode 100644 index 0000000..3527825 --- /dev/null +++ b/planning/phase002 @@ -0,0 +1,28 @@ +# Phase 002: Core Data Model + +## Goal +Create the relational model foundation for users, vehicles, fuel entries, maintenance entries, and maintenance schedules. + +## Work +- Create `User` with `name` and `email`. +- Create `Vehicle` with `make`, `model`, `year`, `color`, `vin`, `licence_plate`, `current_odometer`, and `fuel_tank_size`. +- Create `MaintenanceSchedule` belonging to `Vehicle`. +- Create `FuelEntry` belonging to `Vehicle` and `User` as `updated_by_user`. +- Create `MaintenanceEntry` belonging to `Vehicle` and `User` as `updated_by_user`. +- Add validations for required fields and basic numeric constraints. +- Add model specs for associations, validations, and simple behavior. + +## Notes +- Correct likely typo from design: `licence_place` should probably be `licence_plate`. +- Correct likely typo from design: `MainetnanceEntry` should be `MaintenanceEntry`. + +## Deliverables +- Database migrations. +- ActiveRecord models. +- Model specs. +- Factories. + +## Acceptance Criteria +- All model specs pass. +- Associations match the design. +- Invalid records are rejected with useful validation errors. diff --git a/planning/phase003 b/planning/phase003 new file mode 100644 index 0000000..1e4666c --- /dev/null +++ b/planning/phase003 @@ -0,0 +1,22 @@ +# Phase 003: Vehicle CRUD And Detail Page + +## Goal +Build the main vehicle management interface. + +## Work +- Add `VehiclesController`. +- Add routes for vehicle index, show, new, create, edit, update, and destroy. +- Build simple ERB views for vehicle CRUD. +- Make `vehicles#index` the root page. +- On `vehicles#show`, display vehicle details, maintenance schedules, fuel history, and maintenance history. +- Keep layout simple and mobile-friendly. + +## Deliverables +- Vehicle CRUD UI. +- Vehicle request specs. +- Basic navigation. + +## Acceptance Criteria +- Users can create, view, update, and delete vehicles. +- Vehicle show page is the main operational screen. +- Request specs cover successful and invalid create/update paths. diff --git a/planning/phase004 b/planning/phase004 new file mode 100644 index 0000000..a619798 --- /dev/null +++ b/planning/phase004 @@ -0,0 +1,25 @@ +# Phase 004: Fuel And Maintenance Event Entry + +## Goal +Allow users to record fuel and maintenance events from the vehicle detail page. + +## Work +- Add nested routes under vehicles for fuel entries and maintenance entries. +- Add create forms on `vehicles#show`. +- Save each fuel submission as a new `FuelEntry`. +- Save each maintenance submission as a new `MaintenanceEntry`. +- Update `Vehicle.current_odometer` when a fuel entry is created if the submitted odometer is newer. +- Track `updated_by_user_id` on each event. +- Add request specs and model specs for event creation. + +## Deliverables +- Fuel entry creation flow. +- Maintenance entry creation flow. +- Odometer update behavior. +- Event history display on vehicle show page. + +## Acceptance Criteria +- Fuel submissions create records and update vehicle odometer. +- Maintenance submissions create records. +- Invalid submissions re-render or redirect with validation errors. +- Every event records the submitting user. diff --git a/planning/phase005 b/planning/phase005 new file mode 100644 index 0000000..0a3f7a6 --- /dev/null +++ b/planning/phase005 @@ -0,0 +1,24 @@ +# Phase 005: Lightweight Internal Authentication + +## Goal +Implement simple internal identity tracking without passwords. + +## Work +- Add a monthly access key concept for initial identification. +- Add a flow where a person enters/scans the current key, provides name/email, and receives a session. +- Store only a session user id/token in the browser after verification. +- Add `Current.user` or controller helper methods for accessing the active user. +- Require an identified user before creating fuel or maintenance entries. +- Keep admin/regular user roles out of scope for now. + +## Deliverables +- Login/identification page. +- Session creation and clearing. +- User creation or lookup by email. +- Controller specs/request specs for authenticated and unauthenticated flows. + +## Acceptance Criteria +- Passwords are not used. +- Raw QR/monthly key is not required on every request after session creation. +- Fuel and maintenance submissions are attributed to the active user. +- Unidentified users cannot submit entries. diff --git a/planning/phase006 b/planning/phase006 new file mode 100644 index 0000000..f6d729f --- /dev/null +++ b/planning/phase006 @@ -0,0 +1,24 @@ +# Phase 006: Maintenance Schedules And Overdue Alerts + +## Goal +Use maintenance schedules and event history to show overdue maintenance status. + +## Work +- Add CRUD for vehicle maintenance schedules. +- On `vehicles#show`, compare each schedule against maintenance history. +- Mark a schedule overdue when either mileage interval or time interval is exceeded. +- Use the matching maintenance entry description/type as the history key. +- Log overdue notifications to Rails logger for now. +- Keep external notification service integration stubbed and isolated. + +## Deliverables +- Maintenance schedule management. +- Overdue calculation logic. +- Vehicle show alerts. +- Specs for mileage-based, time-based, and not-overdue cases. + +## Acceptance Criteria +- Vehicle show page displays overdue alerts. +- Mileage and time intervals are treated as an OR condition. +- Notification behavior is logged, not sent externally. +- Overdue logic is covered by tests. diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..2875d99 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,77 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +require 'spec_helper' +ENV['RAILS_ENV'] ||= 'test' +require_relative '../config/environment' +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +# Uncomment the line below in case you have `--require rails_helper` in the `.rspec` file +# that will avoid rails generators crashing because migrations haven't been run yet +# return unless Rails.env.test? +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Rails.root.glob('spec/support/**/*.rb').sort_by(&:to_s).each { |f| require f } + +# Ensures that the test database schema matches the current schema file. +# If there are pending migrations it will invoke `db:test:prepare` to +# recreate the test database by loading the schema. +# If you are not using ActiveRecord, you can remove these lines. +begin + ActiveRecord::Migration.maintain_test_schema! +rescue ActiveRecord::PendingMigrationError => e + abort e.to_s.strip +end +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_paths = [ + Rails.root.join('spec/fixtures') + ] + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # You can uncomment this line to turn off ActiveRecord support entirely. + # config.use_active_record = false + + # RSpec Rails uses metadata to mix in different behaviours to your tests, + # for example enabling you to call `get` and `post` in request specs. e.g.: + # + # RSpec.describe UsersController, type: :request do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://rspec.info/features/8-0/rspec-rails + # + # You can also infer these behaviours automatically by location, e.g. + # /spec/models would pull in the same behaviour as `type: :model` but this + # behaviour is considered legacy and will be removed in a future version. + # + config.infer_spec_type_from_file_location! + + config.include FactoryBot::Syntax::Methods + + config.before(:each, type: :system) do + driven_by :rack_test + end + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/spec/requests/home_spec.rb b/spec/requests/home_spec.rb new file mode 100644 index 0000000..beb6d8c --- /dev/null +++ b/spec/requests/home_spec.rb @@ -0,0 +1,12 @@ +require "rails_helper" + +RSpec.describe "Home" do + describe "GET /" do + it "returns a successful response" do + get root_path + + expect(response).to have_http_status(:ok) + expect(response.body).to include("Car Tracker") + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..327b58e --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,94 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # https://rspec.info/features/3-12/rspec-core/configuration/zero-monkey-patching-mode/ + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/spec/system/home_spec.rb b/spec/system/home_spec.rb new file mode 100644 index 0000000..48c536a --- /dev/null +++ b/spec/system/home_spec.rb @@ -0,0 +1,10 @@ +require "rails_helper" + +RSpec.describe "Home" do + it "shows the placeholder landing page" do + visit root_path + + expect(page).to have_content("Car Tracker") + expect(page).to have_content("Vehicle maintenance and fuel tracking will be added here.") + end +end