Skip to content

Commit 1d45da0

Browse files
authored
Merge pull request #5 from ggerman/fix/update-imptove-tests
fixing and update tests
2 parents c023188 + 31a02f6 commit 1d45da0

11 files changed

Lines changed: 328 additions & 145 deletions

File tree

.github/workflows/test.yml

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,34 @@ jobs:
1414
test:
1515
name: Run Tests
1616
runs-on: ubuntu-latest
17-
17+
1818
strategy:
1919
matrix:
2020
ruby-version: ['3.2', '3.3']
21-
21+
2222
steps:
2323
- name: Checkout code
2424
uses: actions/checkout@v4
25-
26-
- name: Install system dependencies (libgd)
25+
26+
- name: Install system dependencies
2727
run: |
2828
sudo apt-get update
2929
sudo apt-get install -y libgd-dev pkg-config
30-
30+
3131
- name: Set up Ruby ${{ matrix.ruby-version }}
3232
uses: ruby/setup-ruby@v1
3333
with:
3434
ruby-version: ${{ matrix.ruby-version }}
3535
bundler-cache: true
36-
36+
3737
- name: Install gems
3838
run: bundle install
39-
39+
4040
- name: Run tests
41+
env:
42+
GH_TOKEN: fake-token-for-tests
4143
run: bundle exec rake test
42-
44+
4345
- name: Upload test coverage
4446
uses: actions/upload-artifact@v4
4547
with:
@@ -50,55 +52,55 @@ jobs:
5052
lint:
5153
name: Lint Code
5254
runs-on: ubuntu-latest
53-
55+
5456
steps:
5557
- name: Checkout code
5658
uses: actions/checkout@v4
57-
58-
- name: Install system dependencies (libgd)
59+
60+
- name: Install system dependencies
5961
run: |
6062
sudo apt-get update
6163
sudo apt-get install -y libgd-dev pkg-config
62-
64+
6365
- name: Set up Ruby
6466
uses: ruby/setup-ruby@v1
6567
with:
6668
ruby-version: '3.3'
6769
bundler-cache: true
68-
70+
6971
- name: Install gems
7072
run: bundle install
71-
73+
7274
- name: Run rubocop
7375
run: bundle exec rubocop || true
7476

7577
security:
7678
name: Security Scan
7779
runs-on: ubuntu-latest
78-
80+
7981
steps:
8082
- name: Checkout code
8183
uses: actions/checkout@v4
82-
83-
- name: Install system dependencies (libgd)
84+
85+
- name: Install system dependencies
8486
run: |
8587
sudo apt-get update
8688
sudo apt-get install -y libgd-dev pkg-config
87-
89+
8890
- name: Set up Ruby
8991
uses: ruby/setup-ruby@v1
9092
with:
9193
ruby-version: '3.3'
9294
bundler-cache: true
93-
95+
9496
- name: Install gems
9597
run: bundle install
96-
98+
9799
- name: Run Brakeman
98100
run: bundle exec brakeman --no-pager --format json > brakeman.json || true
99-
101+
100102
- name: Upload security report
101103
uses: actions/upload-artifact@v4
102104
with:
103105
name: security-report
104-
path: brakeman.json
106+
path: brakeman.json

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,6 @@ group :development, :test do
1515
gem 'simplecov', '~> 0.22', require: false
1616
gem 'rubocop', '~> 1.50', require: false
1717
gem 'brakeman', '~> 6.0', require: false
18+
gem 'webmock', '~> 3.19'
19+
gem 'vcr', '~> 6.2'
1820
end

Rakefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ require 'rake/testtask'
55
Rake::TestTask.new(:test) do |t|
66
t.libs << 'test'
77
t.libs << 'lib'
8-
t.test_files = FileList['test/**/test_*.rb']
8+
t.test_files = FileList['test/**/test_*.rb'].exclude('test/test_helper.rb')
99
t.verbose = true
1010
t.warning = false
1111
end

lib/analytics/trend_analyzer.rb

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,86 @@
1+
# frozen_string_literal: true
2+
13
module Analytics
24
class TrendAnalyzer
35
attr_reader :values, :dates
4-
def initialize(values, dates)
6+
7+
def initialize(values, dates = nil)
58
@values = values.map(&:to_f)
69
@dates = dates
710
end
8-
11+
12+
def total
13+
@values.sum.round
14+
end
15+
16+
def average
17+
@values.empty? ? 0 : (@values.sum / @values.size).round
18+
end
19+
920
def wow_growth
1021
return 0 if @values.size < 14
11-
curr = @values.last(7).sum
12-
prev = @values[-14..-8].sum
13-
prev.zero? ? 0 : ((curr - prev) / prev * 100).round(1)
14-
end
15-
16-
def total; @values.sum.round; end
17-
def average; (@values.sum / @values.size).round; end
18-
22+
23+
current_week = @values.last(7).sum
24+
previous_week = @values[-14..-8].sum
25+
return 0 if previous_week.zero?
26+
27+
((current_week - previous_week) / previous_week * 100).round(1)
28+
end
29+
30+
def mom_growth
31+
return 0 if @values.size < 60
32+
33+
current_month = @values.last(30).sum
34+
previous_month = @values[-60..-31].sum
35+
return 0 if previous_month.zero?
36+
37+
((current_month - previous_month) / previous_month * 100).round(1)
38+
end
39+
40+
def growth_rate
41+
return 0 if @values.size < 2
42+
43+
first = @values.first
44+
last = @values.last
45+
return 0 if first.zero?
46+
47+
periods = @values.size - 1
48+
((last / first) ** (1.0 / periods) - 1) * 100
49+
end
50+
51+
def peak_day
52+
return nil if @values.empty?
53+
54+
max_value = @values.max
55+
index = @values.index(max_value)
56+
57+
result = { value: max_value.round, index: index }
58+
result[:date] = @dates[index] if @dates
59+
result
60+
end
61+
1962
def forecast(days = 30)
2063
return [] if @values.size < 7
64+
2165
x = (0...@values.size).to_a
2266
y = @values
2367
n = x.size
2468
sum_x = x.sum
2569
sum_y = y.sum
2670
sum_xy = x.zip(y).sum { |xi, yi| xi * yi }
2771
sum_xx = x.sum { |xi| xi * xi }
72+
2873
slope = (n * sum_xy - sum_x * sum_y).to_f / (n * sum_xx - sum_x * sum_x)
2974
intercept = (sum_y - slope * sum_x) / n
75+
3076
last_x = @values.size - 1
3177
(1..days).map { |i| [slope * (last_x + i) + intercept, 0].max.round }
3278
end
79+
80+
def moving_average(window = 7)
81+
return [] if @values.size < window
82+
83+
@values.each_cons(window).map { |slice| slice.sum / window.to_f }
84+
end
3385
end
34-
end
86+
end

lib/charts/professional_chart.rb

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@ def initialize(width: 900, height: 400, title: '', bg_color: '#ffffff', font_pat
1212
end
1313

1414
def render_line_chart(values, labels, output_path)
15-
return if values.empty? || values.all?(&:zero?)
15+
# Validaciones
16+
return nil if values.empty?
17+
return nil if values.size == 1 # Necesitamos al menos 2 puntos para una línea
18+
19+
# Verificar si todos los valores son cero
20+
return nil if values.all? { |v| v == 0 }
1621

1722
img = GD::Image.new(@width, @height)
1823

@@ -82,9 +87,11 @@ def render_line_chart(values, labels, output_path)
8287
[x.to_i, y.to_i]
8388
end
8489

85-
# Area fill
86-
area_points = [[left_margin, @height - bottom_margin]] + points + [[@width - right_margin, @height - bottom_margin]]
87-
img.filled_polygon(area_points, area_color) if area_points.size >= 3
90+
# Area fill (solo si hay puntos válidos)
91+
if points.size >= 2
92+
area_points = [[left_margin, @height - bottom_margin]] + points + [[@width - right_margin, @height - bottom_margin]]
93+
img.filled_polygon(area_points, area_color) if area_points.size >= 3
94+
end
8895

8996
# Line
9097
points.each_cons(2) do |p1, p2|

test/fixtures/sample_config.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
repositories:
3+
- owner: test
4+
name: test-repo
5+
display_name: Test Repository
6+
description: A test repository for metrics
7+
color: "#4a90e2"
8+
9+
dashboard:
10+
title: Test Dashboard
11+
subtitle: Test metrics
12+
update_frequency: Weekly
13+
14+
charts:
15+
default_height: 400
16+
default_width: 800
17+
theme:
18+
background: "#ffffff"
19+
primary: "#4a90e2"
20+
21+
metrics:
22+
retention_days: 365
23+
forecast_days: 30

test/fixtures/sample_views.csv

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
date,count,uniques
2+
2026-03-01,100,45
3+
2026-03-02,120,50
4+
2026-03-03,110,48
5+
2026-03-04,130,55
6+
2026-03-05,140,60
7+
2026-03-06,125,52
8+
2026-03-07,115,49

test/test_analytics.rb

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,84 @@
11
# frozen_string_literal: true
22

33
require_relative 'test_helper'
4+
require_relative '../lib/analytics/trend_analyzer'
45

56
class TestAnalytics < Minitest::Test
67
def setup
78
@sample_views = [100, 110, 120, 115, 130, 125, 140, 135, 150, 145]
89
@sample_dates = (1..10).map { |i| Date.today - i }
10+
@analyzer = Analytics::TrendAnalyzer.new(@sample_views, @sample_dates)
911
end
1012

1113
def test_total_calculation
12-
total = @sample_views.sum
13-
assert_equal 1270, total
14+
assert_equal 1270, @analyzer.total
1415
end
1516

1617
def test_average_calculation
17-
avg = @sample_views.sum / @sample_views.size.to_f
18-
assert_in_delta 127.0, avg, 0.1
18+
assert_equal 127, @analyzer.average
1919
end
2020

21-
def test_wow_growth
22-
# Simulate 14+ days of data
23-
views = [100, 110, 120, 130, 140, 150, 160, # week 1
24-
110, 115, 125, 135, 145, 155, 165] # week 2
25-
26-
current_week = views.last(7).sum
27-
previous_week = views[-14..-8].sum
28-
growth = ((current_week - previous_week) * 100 / previous_week).round(1)
29-
30-
assert_in_delta 3.7, growth, 0.1
21+
def test_empty_values_total
22+
analyzer = Analytics::TrendAnalyzer.new([])
23+
assert_equal 0, analyzer.total
3124
end
3225

33-
def test_empty_data_handling
34-
assert_equal 0, [].sum
35-
assert_equal 0, [].size
26+
def test_empty_values_average
27+
analyzer = Analytics::TrendAnalyzer.new([])
28+
assert_equal 0, analyzer.average
29+
end
30+
31+
def test_wow_growth_with_sufficient_data
32+
# 14+ days of data: week1 = [100,110,120,130,140,150,160] sum = 910
33+
# week2 = [110,115,125,135,145,155,165] sum = 950
34+
# growth = (950 - 910) / 910 * 100 = 4.4%
35+
views = [100, 110, 120, 130, 140, 150, 160,
36+
110, 115, 125, 135, 145, 155, 165]
37+
analyzer = Analytics::TrendAnalyzer.new(views)
38+
assert_in_delta 4.4, analyzer.wow_growth, 0.1
39+
end
40+
41+
def test_wow_growth_with_insufficient_data
42+
analyzer = Analytics::TrendAnalyzer.new([100, 200, 300])
43+
assert_equal 0, analyzer.wow_growth
44+
end
45+
46+
def test_mom_growth_with_sufficient_data
47+
# 60+ days of data (simplified)
48+
views = [10] * 30 + [15] * 30
49+
analyzer = Analytics::TrendAnalyzer.new(views)
50+
assert_in_delta 50.0, analyzer.mom_growth, 0.1
51+
end
52+
53+
def test_growth_rate
54+
views = [100, 110, 121] # 10% growth each period
55+
analyzer = Analytics::TrendAnalyzer.new(views)
56+
# (121/100)^(1/2) - 1 = 0.1 = 10%
57+
assert_in_delta 10.0, analyzer.growth_rate, 0.5
58+
end
59+
60+
def test_peak_day
61+
peak = @analyzer.peak_day
62+
assert_equal 150, peak[:value]
63+
assert_equal 8, peak[:index]
64+
end
65+
66+
def test_forecast_returns_array
67+
forecast = @analyzer.forecast(5)
68+
assert_equal 5, forecast.size
69+
assert forecast.all? { |v| v.is_a?(Numeric) }
70+
end
71+
72+
def test_forecast_with_insufficient_data
73+
analyzer = Analytics::TrendAnalyzer.new([1, 2, 3])
74+
assert_equal [], analyzer.forecast(5)
75+
end
76+
77+
def test_moving_average
78+
ma = @analyzer.moving_average(3)
79+
# First MA: (100+110+120)/3 = 110
80+
# Second: (110+120+115)/3 = 115
81+
assert_in_delta 110.0, ma[0], 0.1
82+
assert_in_delta 115.0, ma[1], 0.1
3683
end
3784
end

0 commit comments

Comments
 (0)