timet 1.5.4 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d3673e96ff9410f3b77d1ecdd59b74cbfe794bd50b9f6fad75701200c911742
4
- data.tar.gz: d405e584648c18a4119b7f4c242923b74e28e433779ad3929ff22da3e569bba1
3
+ metadata.gz: d5bbd4564baa142a2bf20160bdd2499f8df24ce2357eb03b8f1d05a26341fac5
4
+ data.tar.gz: 2fe4c7eb71ab3ff20dc4691caf0bbfad9441e7cf3b1414caaf89ac7571ed4592
5
5
  SHA512:
6
- metadata.gz: 63267d2db22ceabb15c2ca4e8114a25854bf71446799992de552ce91f6ff7d8487fb7204f1ea76df8be77dcfa71ac89d21f26084c87fb17831469406920f1862
7
- data.tar.gz: 2f7d784e8f9b3385d37c90a04fce2b4ea869c5fb3b1e62be613e835b898a2b6f4f8742dbb38785295e695309c7b6b6f6b3fe804a8e5afb7f03655510612ce05d
6
+ metadata.gz: c7d324fe2b3f0aa476caa77cb44414206f6f7c723d47c78a8b9ffd5f5d1319d994520dee9c044d4cb145a2528abf00427c9198a122fefd3e0c85eeff3dcdb5ea
7
+ data.tar.gz: d9a91d46ef64f42d03d2dcc8356338d4fb27f9f81a7bce5ebc100b008476083239b1b7dbdfe599187e3b2f545ea55211f87b347e5a86a7a864d6d39d3b20e5b3
data/.rubocop.yml CHANGED
@@ -6,12 +6,15 @@ AllCops:
6
6
  - timet.gemspec
7
7
 
8
8
  Metrics/MethodLength:
9
- Max: 12
9
+ Max: 15
10
10
 
11
- require: rubocop-rspec
11
+ plugins: rubocop-rspec
12
12
 
13
13
  RSpec/ExampleLength:
14
- Max: 10
14
+ Max: 15
15
15
 
16
16
  Metrics/CyclomaticComplexity:
17
17
  Max: 8
18
+
19
+ RSpec/MultipleMemoizedHelpers:
20
+ Max: 15
data/CHANGELOG.md CHANGED
@@ -1,8 +1,63 @@
1
- ## [Unreleased]
1
+ ## [1.5.6] - 2025-04-07
2
+
3
+ **Improvements:**
4
+
5
+ - **Enhanced Edit Command:** The `edit` command now interactively prompts the user for both the field to edit and its new value if they are not provided as command-line arguments.
6
+ - **Improved Database Initialization:** Database initialization logic has been extracted into a separate `initialize_database` method within the `Application` class, improving code organization and readability.
7
+ - **Refactored `validate_and_update`:** The `validate_and_update` method in `ValidationEditHelper` has been refactored to handle both time fields and other fields more robustly. It now raises an `ArgumentError` for invalid fields and returns the updated item.
8
+ - **Enhanced Security:** The `update_item` method in the `Database` class now utilizes parameterized queries to prevent SQL injection vulnerabilities.
9
+ - **Improved Data Integrity:** The `update_time_columns` method in the `Database` class now correctly updates the `updated_at` and `created_at` columns to reflect the current time.
10
+ - **Code Clarity:** Increased the `Metrics/MethodLength` max length to 15 in `.rubocop.yml` to accommodate refactored code.
11
+ - **Dependency Updates:**
12
+ - Updated `aws-sdk-s3` to version 1.183.
13
+ - Updated `csv` to version 3.3.3.
14
+ - Updated `diff-lcs` to version 1.6.1.
15
+ - Updated `json` to version 2.10.2.
16
+ - Updated `parser` to version 3.3.7.4.
17
+ - Updated `rubocop` to version 1.75.2.
18
+ - Updated `rubocop-ast` to version 1.44.0.
19
+ - Added `prism` to version 1.4.0.
20
+ - Added `logger` to version 1.7.0.
21
+ - **Test Coverage:** Added new tests to `application_spec.rb` to cover the `edit` command, and to `database_spec.rb` to cover the `update_item` and `update_time_columns` methods. Refactored tests in `validation_edit_helper_spec.rb` to cover changes in `validate_and_update`.
22
+ - **Git Ignore:** Added `.qodo` to `.gitignore` to prevent it from being added to the repository.
23
+
24
+ **Bug Fixes:**
25
+
26
+ - **Edit Command:** The `edit` command now correctly updates the database with the new value.
27
+ - **`update_item`:** The `update_item` method is no longer vulnerable to SQL injection.
28
+ - **`update_time_columns`:** The `update_time_columns` method now correctly updates the `updated_at` and `created_at` columns.
29
+ - **.qodo file:** The .qodo file is now ignored by git.
30
+
31
+ ## [1.5.5] - 2025-02-26
32
+
33
+ **Improvements:**
34
+
35
+ - Refactored `DatabaseSyncer` specs to improve clarity, maintainability, and reduce redundancy.
36
+ - Removed `#process_existing_item` as its logic was redundant.
37
+ - Improved `#remote_wins?` tests with `let` blocks and better descriptions.
38
+ - Split multi-expectation tests into smaller, focused tests.
39
+ - Improved test descriptions for accuracy and clarity.
40
+ - Reorganized tests into more logical `describe` blocks.
41
+ - Moved S3 download tests to the `Timet::S3Supabase` `describe` block.
42
+ - Improved CSV export tests with extracted common data, centralized database setup, and a helper method.
43
+ - Improved message expectations in `#export_report` using `class_spy` and `have_received`.
44
+ - Improved test readability in `ApplicationHelper` with `let` blocks and separate `it` blocks.
45
+ - Refactored `DatabaseSyncer` specs with `let` blocks, focused examples, and explicit `expect` assertions.
46
+ - Prefer `have_received` for setting message expectations.
47
+ - Improved granularity in specs with smaller, focused examples and shared test data.
48
+ - Updated gem dependencies to latest versions.
49
+ - Overall code cleanup and improved test structure.
50
+ - Update the way rubocop plugins are defined in the config.
51
+ - Removed unnecessary comments.
52
+
53
+ **Bug Fixes:**
54
+
55
+ - Fixed a bug in the time field update logic in `validation_edit_helper`, specifically regarding the end time.
2
56
 
3
57
  ## [1.5.4] - 2025-02-11
4
58
 
5
59
  **Improvements:**
60
+
6
61
  - Added `.env` file creation in CI workflow for testing environment variables.
7
62
  - Updated Code Climate coverage reporting to use `simplecov-lcov` for LCOV format compatibility.
8
63
  - Refactored validation error message tests in `ValidationEditHelper` for clarity and maintainability.
@@ -16,6 +71,7 @@
16
71
  - Improved error handling and added tests for `S3Supabase`.
17
72
 
18
73
  **Bug Fixes:**
74
+
19
75
  - Fixed environment variable validation in `S3Supabase` to handle `nil` values.
20
76
  - Resolved issues with database synchronization logic in `DatabaseSyncer`.
21
77
  - Fixed test setup and cleanup in `S3Supabase` and `DatabaseSyncer` specs.
@@ -49,22 +49,49 @@ module Timet
49
49
 
50
50
  BUCKET = 'timet'
51
51
 
52
- def initialize(*args)
53
- super
52
+ no_commands do
53
+ # Initializes the database connection based on the provided options.
54
+ #
55
+ # This method determines how to initialize the database connection based on the options provided.
56
+ # It supports injecting a database instance for testing, using a fallback for RSpec, and initializing
57
+ # a production database based on valid command arguments.
58
+ #
59
+ # @param options [Hash] A hash of options that may include a :database key for injecting a database instance.
60
+ # @option options [Database] :database An instance of the Database class to be used directly.
61
+ # @option options [Hash] :current_command The current command being executed, used to validate production
62
+ # database initialization.
63
+ #
64
+ # @return [void] This method does not return a value; it initializes the `@db` instance variable.
65
+ #
66
+ # @raise [SystemExit] If invalid arguments are provided for production database initialization.
67
+ def initialize_database(options)
68
+ db_from_options = options[:database]
54
69
 
55
- if defined?(RSpec)
56
- @db = Database.new
57
- else
58
- command_name = args.dig(2, :current_command, :name)
59
- if VALID_ARGUMENTS.include?(command_name)
70
+ # Allow injecting a database instance, primarily for testing
71
+ if db_from_options
72
+ @db = db_from_options
73
+ elsif defined?(RSpec)
74
+ # Fallback for RSpec if not injected (though injection is preferred)
60
75
  @db = Database.new
61
76
  else
62
- warn 'Invalid arguments provided. Please check your input.'
63
- exit(1)
77
+ # Production database initialization
78
+ command_name = options.dig(:current_command, :name)
79
+ if VALID_ARGUMENTS.include?(command_name)
80
+ @db = Database.new
81
+ else
82
+ warn 'Invalid arguments provided. Please check your input.'
83
+ exit(1)
84
+ end
64
85
  end
65
86
  end
66
87
  end
67
88
 
89
+ def initialize(*args)
90
+ super
91
+ options = args[2] || {} # Third argument is the options hash
92
+ initialize_database(options)
93
+ end
94
+
68
95
  desc "start [tag] --notes='' --pomodoro=[min]",
69
96
  'Start time tracking for a task labeled with the provided [tag], notes and "pomodoro time"
70
97
  in minutes (optional).
@@ -219,13 +246,14 @@ module Timet
219
246
  return puts "No tracked time found for id: #{id}" unless item
220
247
 
221
248
  display_item(item)
222
- unless FIELD_INDEX.keys.include?(field&.downcase) || new_value
249
+ if field.nil? || new_value.nil?
223
250
  field = select_field_to_edit
224
251
  new_value = prompt_for_new_value(item, field)
225
252
  end
226
253
 
227
254
  updated_item = validate_and_update(item, field, new_value)
228
- display_item(updated_item || item)
255
+ @db.update_item(id, field, updated_item[FIELD_INDEX[field]])
256
+ display_item(updated_item)
229
257
  end
230
258
 
231
259
  desc 'delete (d) [id]', 'Delete task => tt d 23'
@@ -115,9 +115,7 @@ module Timet
115
115
  #
116
116
  # @note The method executes SQL to update the specified field of the item with the given ID.
117
117
  def update_item(id, field, value)
118
- return if %w[start end].include?(field) && value.nil?
119
-
120
- execute_sql("UPDATE items SET #{field}='#{value}', updated_at=#{Time.now.utc.to_i} WHERE id = #{id}")
118
+ execute_sql("UPDATE items SET #{field} = ?, updated_at = ? WHERE id = ?", [value, Time.now.utc.to_i, id])
121
119
  end
122
120
 
123
121
  # Deletes an item from the items table.
@@ -162,19 +160,21 @@ module Timet
162
160
  result.empty? ? nil : result[0]
163
161
  end
164
162
 
165
- # Finds an item in the items table by its ID.
163
+ # Finds an item by its ID.
166
164
  #
167
- # @param id [Integer] The ID of the item to be found.
165
+ # @param id [Integer] The ID of the item to find.
168
166
  #
169
- # @return [Array, nil] The item as an array, or nil if the item does not exist.
167
+ # @return [Array, nil] The item as an array if found, nil otherwise.
170
168
  #
171
169
  # @example Find an item with ID 1
172
- # find_item(1)
170
+ # find_item(1) # => [1, 1678886400, 1678890000, 'work', 'notes', nil, 1678890000, 1678886400, nil]
173
171
  #
174
- # @note The method executes SQL to find the item with the given ID in the 'items' table.
172
+ # @note The method executes a SQL query to find the item by its ID.
173
+ # @note If the item is found, it returns the item as an array.
174
+ # @note If the item is not found, it returns nil.
175
175
  def find_item(id)
176
- result = execute_sql('SELECT * FROM items WHERE id = ? AND (deleted IS NULL OR deleted = 0)', [id])
177
- result.empty? ? nil : result[0]
176
+ result = execute_sql('SELECT * FROM items WHERE id = ?', [id])
177
+ result.first.dup if result.any? # Add .dup to create a copy
178
178
  end
179
179
 
180
180
  # Fetches all items from the items table that have a start time greater than or equal to today.
@@ -316,7 +316,7 @@ module Timet
316
316
  result.each do |item|
317
317
  id = item[0]
318
318
  end_time = item[2]
319
- execute_sql("UPDATE items SET updated_at = #{end_time}, created_at = #{end_time} WHERE id = #{id}")
319
+ execute_sql('UPDATE items SET updated_at = ?, created_at = ? WHERE id = ?', [end_time, end_time, id])
320
320
  end
321
321
  end
322
322
  end
@@ -9,32 +9,42 @@ module Timet
9
9
  # Constants for time fields.
10
10
  TIME_FIELDS = %w[start end].freeze
11
11
 
12
- # Validates and updates a tracking item's field with a new value.
12
+ # Validates and updates an item's attribute based on the provided field and new value.
13
13
  #
14
- # @param item [Array] The tracking item to be updated.
14
+ # @param item [Array] The item to be updated.
15
15
  # @param field [String] The field to be updated.
16
- # @param new_value [String, nil] The new value to be set for the specified field.
17
- #
18
- # @return [Array, nil] The updated tracking item if the update was successful, otherwise nil.
16
+ # @param new_value [String] The new value for the field.
19
17
  #
20
- # @example Validate and update the 'notes' field of a tracking item
21
- # validate_and_update(item, 'notes', 'Updated notes')
18
+ # @return [Array] The updated item.
22
19
  #
23
- # @note The method checks if the field is a time field (start or end) and processes it accordingly.
24
- # @note If the field is not a time field, it directly updates the field with the new value.
25
- # @note The method returns the updated tracking item if the update was successful.
20
+ # @raise [ArgumentError] If the field is invalid or the new value is invalid.
26
21
  def validate_and_update(item, field, new_value)
27
- return if new_value.nil?
28
-
29
- id = item[0]
30
-
31
- if TIME_FIELDS.include?(field)
32
- process_and_update_time_field(item, field, new_value, id)
22
+ case field
23
+ when 'notes'
24
+ item[4] = new_value
25
+ when 'tag'
26
+ item[3] = new_value
27
+ when 'start'
28
+ item[1] = validate_time(new_value)
29
+ when 'end'
30
+ item[2] = validate_time(new_value)
33
31
  else
34
- @db.update_item(id, field, new_value)
32
+ raise ArgumentError, "Invalid field: #{field}"
35
33
  end
34
+ item
35
+ end
36
36
 
37
- @db.find_item(id)
37
+ # Validates if a given time string is in a valid format.
38
+ #
39
+ # @param time_str [String] The time string to validate.
40
+ #
41
+ # @return [Integer] The validated time as an integer.
42
+ #
43
+ # @raise [ArgumentError] If the time string is not in a valid format.
44
+ def validate_time(time_str)
45
+ Time.parse(time_str).to_i
46
+ rescue ArgumentError
47
+ raise ArgumentError, "Invalid time format: #{time_str}"
38
48
  end
39
49
 
40
50
  private
@@ -92,9 +102,9 @@ module Timet
92
102
  def update_time_field(item, field, new_time)
93
103
  field_index = Timet::Application::FIELD_INDEX[field]
94
104
  timestamp = item[field_index]
95
- current_time = Time.at(timestamp || TimeHelper.current_timestamp).to_s.split
96
- current_time[1] = new_time
97
- DateTime.strptime(current_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
105
+ edit_time = Time.at(timestamp || item[1]).to_s.split
106
+ edit_time[1] = new_time
107
+ DateTime.strptime(edit_time.join(' '), '%Y-%m-%d %H:%M:%S %z').to_time
98
108
  end
99
109
 
100
110
  # Validates if a new time value is valid for a specific time field (start or end).
@@ -116,9 +126,9 @@ module Timet
116
126
  item_after_start = fetch_item_after_start(id)
117
127
 
118
128
  if field == 'start'
119
- new_value_epoch >= item_before_end && new_value_epoch <= item_end
129
+ new_value_epoch.between?(item_before_end, item_end)
120
130
  else
121
- new_value_epoch >= item_start && new_value_epoch <= item_after_start
131
+ new_value_epoch.between?(item_start, item_after_start)
122
132
  end
123
133
  end
124
134
 
data/lib/timet/version.rb CHANGED
@@ -6,6 +6,6 @@ module Timet
6
6
  # @return [String] The version number in the format 'major.minor.patch'.
7
7
  #
8
8
  # @example Get the version of the Timet application
9
- # Timet::VERSION # => '1.5.4'
10
- VERSION = '1.5.4'
9
+ # Timet::VERSION # => '1.5.6'
10
+ VERSION = '1.5.6'
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.4
4
+ version: 1.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frank Vielma
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-02-11 00:00:00.000000000 Z
11
+ date: 2025-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor