Merge branch 'main' into ticket-bet

This commit is contained in:
Samuel Tariku 2025-05-30 19:39:21 +03:00
commit bab1c6bb4c
33 changed files with 1955 additions and 624 deletions

View File

@ -2,6 +2,7 @@ package main
import (
// "context"
"fmt"
"log/slog"
"os"

View File

@ -203,7 +203,8 @@ CREATE TABLE events (
match_period INT,
is_live BOOLEAN,
status TEXT,
fetched_at TIMESTAMP DEFAULT now()
fetched_at TIMESTAMP DEFAULT now(),
source TEXT DEFAULT 'b365api'
);
CREATE TABLE odds (
id SERIAL PRIMARY KEY,

View File

@ -19,7 +19,8 @@ INSERT INTO events (
added_time,
match_period,
is_live,
status
status,
source
)
VALUES (
$1,
@ -41,7 +42,8 @@ VALUES (
$17,
$18,
$19,
$20
$20,
$21
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
@ -63,6 +65,7 @@ SET sport_id = EXCLUDED.sport_id,
match_period = EXCLUDED.match_period,
is_live = EXCLUDED.is_live,
status = EXCLUDED.status,
source = EXCLUDED.source,
fetched_at = now();
-- name: InsertUpcomingEvent :exec
INSERT INTO events (
@ -80,7 +83,8 @@ INSERT INTO events (
league_cc,
start_time,
is_live,
status
status,
source
)
VALUES (
$1,
@ -97,7 +101,8 @@ VALUES (
$12,
$13,
false,
'upcoming'
'upcoming',
$14
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
@ -114,6 +119,7 @@ SET sport_id = EXCLUDED.sport_id,
start_time = EXCLUDED.start_time,
is_live = false,
status = 'upcoming',
source = EXCLUDED.source,
fetched_at = now();
-- name: ListLiveEvents :many
SELECT id
@ -135,6 +141,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE is_live = false
@ -156,6 +163,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE start_time < now()
@ -197,6 +205,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE is_live = false
@ -235,6 +244,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE id = $1

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: auth.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: bet.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: branch.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: company.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: copyfrom.go
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: events.sql
package dbgen
@ -37,6 +37,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE is_live = false
@ -60,6 +61,7 @@ type GetAllUpcomingEventsRow struct {
StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
}
@ -88,6 +90,7 @@ func (q *Queries) GetAllUpcomingEvents(ctx context.Context) ([]GetAllUpcomingEve
&i.StartTime,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
); err != nil {
return nil, err
@ -116,6 +119,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE start_time < now()
@ -138,6 +142,7 @@ type GetExpiredUpcomingEventsRow struct {
StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
}
@ -166,6 +171,7 @@ func (q *Queries) GetExpiredUpcomingEvents(ctx context.Context) ([]GetExpiredUpc
&i.StartTime,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
); err != nil {
return nil, err
@ -194,6 +200,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE is_live = false
@ -243,6 +250,7 @@ type GetPaginatedUpcomingEventsRow struct {
StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
}
@ -278,6 +286,7 @@ func (q *Queries) GetPaginatedUpcomingEvents(ctx context.Context, arg GetPaginat
&i.StartTime,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
); err != nil {
return nil, err
@ -348,6 +357,7 @@ SELECT id,
start_time,
is_live,
status,
source,
fetched_at
FROM events
WHERE id = $1
@ -372,6 +382,7 @@ type GetUpcomingByIDRow struct {
StartTime pgtype.Timestamp `json:"start_time"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
}
@ -394,6 +405,7 @@ func (q *Queries) GetUpcomingByID(ctx context.Context, id string) (GetUpcomingBy
&i.StartTime,
&i.IsLive,
&i.Status,
&i.Source,
&i.FetchedAt,
)
return i, err
@ -420,7 +432,8 @@ INSERT INTO events (
added_time,
match_period,
is_live,
status
status,
source
)
VALUES (
$1,
@ -442,7 +455,8 @@ VALUES (
$17,
$18,
$19,
$20
$20,
$21
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
@ -464,6 +478,7 @@ SET sport_id = EXCLUDED.sport_id,
match_period = EXCLUDED.match_period,
is_live = EXCLUDED.is_live,
status = EXCLUDED.status,
source = EXCLUDED.source,
fetched_at = now()
`
@ -488,6 +503,7 @@ type InsertEventParams struct {
MatchPeriod pgtype.Int4 `json:"match_period"`
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
Source pgtype.Text `json:"source"`
}
func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error {
@ -512,6 +528,7 @@ func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) error
arg.MatchPeriod,
arg.IsLive,
arg.Status,
arg.Source,
)
return err
}
@ -532,7 +549,8 @@ INSERT INTO events (
league_cc,
start_time,
is_live,
status
status,
source
)
VALUES (
$1,
@ -549,7 +567,8 @@ VALUES (
$12,
$13,
false,
'upcoming'
'upcoming',
$14
) ON CONFLICT (id) DO
UPDATE
SET sport_id = EXCLUDED.sport_id,
@ -566,6 +585,7 @@ SET sport_id = EXCLUDED.sport_id,
start_time = EXCLUDED.start_time,
is_live = false,
status = 'upcoming',
source = EXCLUDED.source,
fetched_at = now()
`
@ -583,6 +603,7 @@ type InsertUpcomingEventParams struct {
LeagueName pgtype.Text `json:"league_name"`
LeagueCc pgtype.Text `json:"league_cc"`
StartTime pgtype.Timestamp `json:"start_time"`
Source pgtype.Text `json:"source"`
}
func (q *Queries) InsertUpcomingEvent(ctx context.Context, arg InsertUpcomingEventParams) error {
@ -600,6 +621,7 @@ func (q *Queries) InsertUpcomingEvent(ctx context.Context, arg InsertUpcomingEve
arg.LeagueName,
arg.LeagueCc,
arg.StartTime,
arg.Source,
)
return err
}

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
package dbgen
@ -198,6 +198,7 @@ type Event struct {
IsLive pgtype.Bool `json:"is_live"`
Status pgtype.Text `json:"status"`
FetchedAt pgtype.Timestamp `json:"fetched_at"`
Source pgtype.Text `json:"source"`
}
type Notification struct {

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: notification.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: odds.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: otp.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: referal.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: result.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: ticket.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: transactions.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: transfer.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: user.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: virtual_games.sql
package dbgen

View File

@ -1,6 +1,6 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.28.0
// sqlc v1.29.0
// source: wallet.sql
package dbgen

2
go.mod
View File

@ -18,6 +18,8 @@ require (
golang.org/x/crypto v0.36.0
)
require github.com/gorilla/websocket v1.5.3 // indirect
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect

View File

@ -23,7 +23,35 @@ type Event struct {
MatchPeriod int
IsLive bool
Status string
Source string
}
type BetResult struct {
Success int `json:"success"`
Pager struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
}
Results []struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"home"`
Away *struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"away"`
} `json:"results"`
}
type UpcomingEvent struct {
ID string // Event ID
SportID string // Sport ID
@ -38,6 +66,7 @@ type UpcomingEvent struct {
LeagueName string // League name
LeagueCC string // League country code
StartTime time.Time // Converted from "time" field in UNIX format
Source string // bet api provider (bet365, betfair)
}
type MatchResult struct {
EventID string

View File

@ -151,3 +151,142 @@ type IceHockeyResultResponse struct {
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
type CricketResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Extra struct {
HomePos string `json:"home_pos"`
AwayPos string `json:"away_pos"`
NumberOfPeriods string `json:"numberofperiods"`
PeriodLength string `json:"periodlength"`
StadiumData map[string]string `json:"stadium_data"`
} `json:"extra"`
HasLineup int `json:"has_lineup"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
type VolleyballResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstSet Score `json:"1"`
SecondSet Score `json:"2"`
ThirdSet Score `json:"3"`
FourthSet Score `json:"4"`
FivethSet Score `json:"5"`
} `json:"scores"`
Stats struct {
PointsWonOnServe []string `json:"points_won_on_serve"`
LongestStreak []string `json:"longest_streak"`
} `json:"stats"`
InplayCreatedAt string `json:"inplay_created_at"`
InplayUpdatedAt string `json:"inplay_updated_at"`
Bet365ID string `json:"bet365_id"`
}
type DartsResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
InplayCreatedAt string `json:"inplay_created_at"`
InplayUpdatedAt string `json:"inplay_updated_at"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}
type FutsalResultResponse struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
TimeStatus string `json:"time_status"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
CC string `json:"cc"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"home"`
Away struct {
ID string `json:"id"`
Name string `json:"name"`
ImageID string `json:"image_id"`
CC string `json:"cc"`
} `json:"away"`
SS string `json:"ss"`
Scores struct {
FirstPeriod Score `json:"1"`
SecondPeriod Score `json:"2"`
ThirdPeriod Score `json:"3"`
TotalScore Score `json:"4"`
} `json:"scores"`
Events []map[string]string `json:"events"`
InplayCreatedAt string `json:"inplay_created_at"`
InplayUpdatedAt string `json:"inplay_updated_at"`
ConfirmedAt string `json:"confirmed_at"`
Bet365ID string `json:"bet365_id"`
}

View File

@ -101,6 +101,72 @@ const (
ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY IceHockeyMarket = 170240
)
type CricketMarket int64
const (
// Main
CRICKET_TO_WIN_THE_MATCH CricketMarket = 1246
CRICKET_TEAM_TOP_BATTER CricketMarket = 1241
CRICKET_TEAM_TOP_BOWLE CricketMarket = 1242
CRICKET_PLAYER_OF_THE_MATCH CricketMarket = 346
CRICKET_FIRST_WICKET_METHOD CricketMarket = 30205
// First Over
CRICKET_FIRST_OVER_TOTAL_RUNS CricketMarket = 300336
CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even CricketMarket = 300118
// Inninigs 1
CRICKET_FIRST_INNINIGS_SCORE CricketMarket = 300338
CRICKET_INNINGS_OF_MATCH_BOWLED_OUT CricketMarket = 300108
// Match
CRICKET_TOP_MATCH_BATTER CricketMarket = 30245
CRICKET_TOP_MATCH_BOWLER CricketMarket = 30246
)
type VolleyballMarket int64
const (
VOLLEYBALL_GAME_LINES VolleyballMarket = 910000
VOLLEYBALL_CORRECT_SET_SCORE VolleyballMarket = 910201
VOLLEYBALL_MATCH_TOTAL_ODD_EVEN VolleyballMarket = 910217
VOLLEYBALL_SET_ONE_LINES VolleyballMarket = 910204
VOLLEYBALL_SET_ONE_TO_GO_TO_EXTRA_POINTS VolleyballMarket = 910209
VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN VolleyballMarket = 910218
)
type DartsMarket int64
const (
// Main
DARTS_MATCH_WINNER DartsMarket = 703 // match_winner
DARTS_MATCH_DOUBLE DartsMarket = 150228 // match_double
DARTS_MATCH_TREBLE DartsMarket = 150230 // match_treble
DARTS_CORRECT_LEG_SCORE DartsMarket = 150015 // correct_leg_score
DARTS_TOTAL_LEGS DartsMarket = 150117 // total_legs
DARTS_MOST_HUNDERED_EIGHTYS DartsMarket = 150030 // "most_180s"
DARTS_TOTAL_HUNDERED_EIGHTYS DartsMarket = 150012 // total_180s
DARTS_MOST_HUNDERED_EIGHTYS_HANDICAP DartsMarket = 150227 // most_180s_handicap
DARTS_PLAYER_HUNDERED_EIGHTYS DartsMarket = 150121 // player_180s
DARTS_FIRST_DART DartsMarket = 150125 // first_dart
)
type FutsalMarket int64
const (
// Main
FUTSAL_GAME_LINES FutsalMarket = 830001
FUTSAL_MONEY_LINE FutsalMarket = 830130
// Others
FUTSAL_DOUBLE_RESULT_9_WAY FutsalMarket = 830124
// Score
FUTSAL_TEAM_TO_SCORE_FIRST FutsalMarket = 830141
FUTSAL_RACE_TO_GOALS FutsalMarket = 830142
)
type AmericanFootballMarket int64
const (
@ -214,6 +280,50 @@ var SupportedMarkets = map[int64]bool{
int64(ICE_HOCKEY_ALTERNATIVE_PUCK_LINE_TWO_WAY): false,
int64(ICE_HOCKEY_ALTERNATIVE_TOTAL_TWO_WAY): false,
// Cricket Markets
int64(CRICKET_TO_WIN_THE_MATCH): true,
int64(CRICKET_FIRST_OVER_TOTAL_RUNS_Odd_Even): false,
int64(CRICKET_FIRST_INNINIGS_SCORE): false,
int64(CRICKET_INNINGS_OF_MATCH_BOWLED_OUT): false,
int64(CRICKET_FIRST_OVER_TOTAL_RUNS): false,
int64(CRICKET_TEAM_TOP_BATTER): false,
int64(CRICKET_TEAM_TOP_BOWLE): false,
int64(CRICKET_PLAYER_OF_THE_MATCH): false,
int64(CRICKET_FIRST_WICKET_METHOD): false,
int64(CRICKET_TOP_MATCH_BATTER): false,
int64(CRICKET_TOP_MATCH_BOWLER): false,
// Volleyball Markets
int64(VOLLEYBALL_GAME_LINES): true,
int64(VOLLEYBALL_CORRECT_SET_SCORE): true,
int64(VOLLEYBALL_MATCH_TOTAL_ODD_EVEN): true,
int64(VOLLEYBALL_SET_ONE_LINES): false,
int64(VOLLEYBALL_SET_ONE_TO_GO_TO_EXTRA_POINTS): false,
int64(VOLLEYBALL_SET_ONE_TOTAL_ODD_EVEN): false,
// Darts Markets
int64(DARTS_MATCH_WINNER): true,
int64(DARTS_TOTAL_LEGS): true,
int64(DARTS_CORRECT_LEG_SCORE): false,
int64(DARTS_MATCH_DOUBLE): false,
int64(DARTS_MATCH_TREBLE): false,
int64(DARTS_MOST_HUNDERED_EIGHTYS): false,
int64(DARTS_TOTAL_HUNDERED_EIGHTYS): false,
int64(DARTS_MOST_HUNDERED_EIGHTYS_HANDICAP): false,
int64(DARTS_PLAYER_HUNDERED_EIGHTYS): false,
int64(DARTS_FIRST_DART): false,
// Futsal Markets
int64(FUTSAL_MONEY_LINE): true,
int64(FUTSAL_GAME_LINES): true,
int64(FUTSAL_TEAM_TO_SCORE_FIRST): true,
int64(FUTSAL_DOUBLE_RESULT_9_WAY): false,
int64(FUTSAL_RACE_TO_GOALS): false,
// American Football Markets
int64(AMERICAN_FOOTBALL_MONEY_LINE): true,
int64(AMERICAN_FOOTBALL_SPREAD): true,

View File

@ -40,6 +40,7 @@ func (s *Store) SaveEvent(ctx context.Context, e domain.Event) error {
MatchPeriod: pgtype.Int4{Int32: int32(e.MatchPeriod), Valid: true},
IsLive: pgtype.Bool{Bool: e.IsLive, Valid: true},
Status: pgtype.Text{String: e.Status, Valid: true},
Source: pgtype.Text{String: e.Source, Valid: true},
})
}
func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) error {
@ -57,6 +58,7 @@ func (s *Store) SaveUpcomingEvent(ctx context.Context, e domain.UpcomingEvent) e
LeagueName: pgtype.Text{String: e.LeagueName, Valid: true},
LeagueCc: pgtype.Text{String: e.LeagueCC, Valid: true},
StartTime: pgtype.Timestamp{Time: e.StartTime, Valid: true},
Source: pgtype.Text{String: e.Source, Valid: true},
})
}
@ -85,6 +87,7 @@ func (s *Store) GetAllUpcomingEvents(ctx context.Context) ([]domain.UpcomingEven
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String,
}
}
return upcomingEvents, nil
@ -112,6 +115,7 @@ func (s *Store) GetExpiredUpcomingEvents(ctx context.Context) ([]domain.Upcoming
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String,
}
}
return upcomingEvents, nil
@ -165,6 +169,7 @@ func (s *Store) GetPaginatedUpcomingEvents(ctx context.Context, limit domain.Val
LeagueName: e.LeagueName.String,
LeagueCC: e.LeagueCc.String,
StartTime: e.StartTime.Time.UTC(),
Source: e.Source.String,
}
}
totalCount, err := s.queries.GetTotalEvents(ctx, dbgen.GetTotalEventsParams{
@ -212,6 +217,7 @@ func (s *Store) GetUpcomingEventByID(ctx context.Context, ID string) (domain.Upc
LeagueName: event.LeagueName.String,
LeagueCC: event.LeagueCc.String,
StartTime: event.StartTime.Time.UTC(),
Source: event.Source.String,
}, nil
}
func (s *Store) UpdateFinalScore(ctx context.Context, eventID, fullScore, status string) error {

View File

@ -30,6 +30,30 @@ func New(token string, store *repository.Store) Service {
}
func (s *service) FetchLiveEvents(ctx context.Context) error {
var wg sync.WaitGroup
urls := []struct {
name string
source string
}{
{"https://api.b365api.com/v1/bet365/inplay?sport_id=%d&token=%s", "bet365"},
{"https://api.b365api.com/v1/betfair/sb/inplay?sport_id=%d&token=%s", "betfair"},
{"https://api.b365api.com/v1/1xbet/inplay?sport_id=%d&token=%s", "1xbet"},
}
for _, url := range urls {
wg.Add(1)
go func() {
defer wg.Done()
s.fetchLiveEvents(ctx, url.name, url.source)
}()
}
wg.Wait()
return nil
}
func (s *service) fetchLiveEvents(ctx context.Context, url, source string) error {
sportIDs := []int{1, 13, 78, 18, 91, 16, 17, 14, 12, 3, 2, 4, 83, 15, 92, 94, 8, 19, 36, 66, 9, 75, 90, 95, 110, 107, 151, 162, 148}
var wg sync.WaitGroup
@ -39,7 +63,7 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
go func(sportID int) {
defer wg.Done()
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/inplay?sport_id=%d&token=%s", sportID, s.token)
url := fmt.Sprintf(url, sportID, s.token)
resp, err := http.Get(url)
if err != nil {
fmt.Printf(" Failed request for sport_id=%d: %v\n", sportID, err)
@ -49,13 +73,41 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
body, _ := io.ReadAll(resp.Body)
events := []domain.Event{}
switch source {
case "bet365":
events = handleBet365prematch(body, sportID)
case "betfair":
events = handleBetfairprematch(body, sportID, source)
case "1xbet":
// betfair and 1xbet have the same result structure
events = handleBetfairprematch(body, sportID, source)
}
for _, event := range events {
if err := s.store.SaveEvent(ctx, event); err != nil {
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
}
}
}(sportID)
}
wg.Wait()
fmt.Println("All live events fetched and stored.")
return nil
}
func handleBet365prematch(body []byte, sportID int) []domain.Event {
var data struct {
Success int `json:"success"`
Results [][]map[string]interface{} `json:"results"`
}
events := []domain.Event{}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body))
return
return events
}
for _, group := range data.Results {
@ -83,33 +135,88 @@ func (s *service) FetchLiveEvents(ctx context.Context) error {
Status: "live",
MatchPeriod: getInt(ev["MD"]),
AddedTime: getInt(ev["TA"]),
Source: "bet365",
}
if err := s.store.SaveEvent(ctx, event); err != nil {
fmt.Printf("Could not store live event [id=%s]: %v\n", event.ID, err)
events = append(events, event)
}
}
}
}(sportID)
}
wg.Wait()
fmt.Println("All live events fetched and stored.")
return nil
return events
}
func handleBetfairprematch(body []byte, sportID int, source string) []domain.Event {
var data struct {
Success int `json:"success"`
Results []map[string]interface{} `json:"results"`
}
events := []domain.Event{}
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
fmt.Printf(" Decode failed for sport_id=%d\nRaw: %s\n", sportID, string(body))
return events
}
for _, ev := range data.Results {
homeRaw, _ := ev["home"].(map[string]interface{})
homeId, _ := homeRaw["id"].(string)
awayRaw, _ := ev["home"].(map[string]interface{})
awayId, _ := awayRaw["id"].(string)
event := domain.Event{
ID: getString(ev["id"]),
SportID: fmt.Sprintf("%d", sportID),
TimerStatus: getString(ev["time_status"]),
HomeTeamID: homeId,
AwayTeamID: awayId,
StartTime: time.Now().UTC().Format(time.RFC3339),
IsLive: true,
Status: "live",
Source: source,
}
events = append(events, event)
}
return events
}
func (s *service) FetchUpcomingEvents(ctx context.Context) error {
sportIDs := []int{1, 18, 17}
var wg sync.WaitGroup
urls := []struct {
name string
source string
}{
{"https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", "bet365"},
{"https://api.b365api.com/v1/betfair/sb/upcoming?sport_id=%d&token=%s&page=%d", "betfair"},
{"https://api.b365api.com/v1/1xbet/upcoming?sport_id=%d&token=%s&page=%d", "1xbet"},
}
for _, sportID := range sportIDs {
for _, url := range urls {
wg.Add(1)
go func() {
defer wg.Done()
s.fetchUpcomingEventsFromProvider(ctx, url.name, url.source)
}()
}
wg.Wait()
return nil
}
func (s *service) fetchUpcomingEventsFromProvider(ctx context.Context, url, source string) {
sportIDs := []int{1, 18, 17}
var totalPages int = 1
var page int = 0
var limit int = 150
var limit int = 100
var count int = 0
for _, sportID := range sportIDs {
for page <= totalPages {
page = page + 1
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/upcoming?sport_id=%d&token=%s&page=%d", sportID, s.token, page)
log.Printf("📡 Fetching data for sport %d event data page %d/%d", sportID, page, min(limit, totalPages))
log.Printf("📡 Fetching data for event data page %d", page)
resp, err := http.Get(url)
if err != nil {
log.Printf("❌ Failed to fetch event data for page %d: %v", page, err)
@ -118,31 +225,8 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data struct {
Success int `json:"success"`
Pager struct {
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
}
Results []struct {
ID string `json:"id"`
SportID string `json:"sport_id"`
Time string `json:"time"`
League struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"league"`
Home struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"home"`
Away *struct {
ID string `json:"id"`
Name string `json:"name"`
} `json:"away"`
} `json:"results"`
}
var data domain.BetResult
if err := json.Unmarshal(body, &data); err != nil || data.Success != 1 {
log.Printf("❌ Failed to parse json data")
continue
@ -182,6 +266,7 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
LeagueName: ev.League.Name,
LeagueCC: "",
StartTime: time.Unix(startUnix, 0).UTC(),
Source: source,
}
if ev.Away != nil {
@ -209,8 +294,6 @@ func (s *service) FetchUpcomingEvents(ctx context.Context) error {
count++
}
}
return nil
}
func getString(v interface{}) string {

View File

@ -182,32 +182,33 @@ func evaluateAsianHandicap(outcome domain.BetOutcome, score struct{ Home, Away i
if err != nil {
return domain.OUTCOME_STATUS_ERROR, err
}
continue
}
} else {
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, err
fmt.Printf("multi outcome check error")
return domain.OUTCOME_STATUS_PENDING, err
}
}
continue
} else if adjustedHomeScore < adjustedAwayScore {
if outcome.OddHeader == "2" {
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_WIN)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, err
}
continue
}
} else {
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_LOSS)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, err
fmt.Printf("multi outcome check error")
return domain.OUTCOME_STATUS_PENDING, err
}
continue
} else if adjustedHomeScore == adjustedAwayScore {
}
}
if newOutcome == domain.OUTCOME_STATUS_PENDING {
newOutcome, err = checkMultiOutcome(newOutcome, domain.OUTCOME_STATUS_VOID)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, err
fmt.Printf("multi outcome check error")
return domain.OUTCOME_STATUS_PENDING, err
}
continue
}
}
return newOutcome, nil
@ -498,9 +499,33 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
}
// Result and Total betting is a type of bet where the bettor predicts
// the outcome of a match and whether the total number of points scored will be over or under a specified number.
func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
func evaluateTotalLegs(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
total, err := strconv.ParseFloat(outcome.OddName, 64)
if err != nil {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid : %s", outcome.OddName)
}
totalLegs := float64(score.Home + score.Away)
switch outcome.OddHeader {
case "Over":
if totalLegs > total {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
case "Under":
if totalLegs < total {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd header: %s", outcome.OddHeader)
}
// Result and Total betting is a type of bet where the bettor predicts
// the outcome of a match and whether the total number of points scored will be over or under a specified number.
func evaluateResultAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// The handicap will be in the format "U {float}" or "O {float}"
// U and O denoting over and under for this case
@ -548,11 +573,11 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
}
}
}
// Team Total betting is a type of bet where the bettor predicts the total number of points scored by a specific team in a match
// is over or under a specified number.
func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Team Total betting is a type of bet where the bettor predicts the total number of points scored by a specific team in a match
// is over or under a specified number.
func evaluateTeamTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// The handicap will be in the format "U {float}" or "O {float}"
// U and O denoting over and under for this case
@ -594,11 +619,11 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed to parse over and under: %s", outcome.OddName)
}
}
}
// Result and Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points
// and also the result fo the match
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Result and Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points
// and also the result fo the match
func evaluateResultAndBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// The name parameter will hold value "name": "{team_name} and {Yes | No}"
// The best way to do this is to evaluate backwards since there might be
@ -623,6 +648,10 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddHeader)
}
// above code removes any space from team name, so do the same for outcome.HomeTeamName and outcome.AwayTeamName
outcome.HomeTeamName = strings.Join(strings.Split(outcome.HomeTeamName, " "), "")
outcome.AwayTeamName = strings.Join(strings.Split(outcome.AwayTeamName, " "), "")
switch teamName {
case outcome.HomeTeamName:
if score.Home > score.Away {
@ -646,10 +675,10 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_LOSS, nil
}
}
// Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points.
func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Both Teams To Score X Points is a type of bet where the bettor predicts whether both teams will score a certain number of points.
func evaluateBTTSX(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
threshold, err := strconv.ParseInt(outcome.OddName, 10, 64)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid threshold: %s", outcome.OddName)
@ -670,10 +699,10 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
}
return domain.OUTCOME_STATUS_LOSS, nil
}
}
// Money Line 3 Way betting is a type of bet where the bettor predicts the outcome of a match with three possible outcomes: home win, away win, or draw.
func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Money Line 3 Way betting is a type of bet where the bettor predicts the outcome of a match with three possible outcomes: home win, away win, or draw.
func evaluateMoneyLine3Way(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddName {
case "1": // Home win
if score.Home > score.Away {
@ -693,22 +722,21 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
default:
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
}
}
}
// Double Result betting is a type of bet where the bettor predicts the outcome of a match at both half-time and full-time.
func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home, Away int }, secondHalfScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
func evaluateDoubleResult(outcome domain.BetOutcome, firstHalfScore struct{ Home, Away int }, fullTimeScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
halfWins := strings.Split(outcome.OddName, "-")
if len(halfWins) != 2 {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid odd name: %s", outcome.OddName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
}
firstHalfWinner := strings.TrimSpace(halfWins[0])
secondHalfWinner := strings.TrimSpace(halfWins[1])
fullTimeWinner := strings.TrimSpace(halfWins[1])
if firstHalfWinner != outcome.HomeTeamName && firstHalfWinner != outcome.AwayTeamName && firstHalfWinner != "Tie" {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
}
if secondHalfWinner != outcome.HomeTeamName && secondHalfWinner != outcome.AwayTeamName && secondHalfWinner != "Tie" {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
if fullTimeWinner != outcome.HomeTeamName && fullTimeWinner != outcome.AwayTeamName && fullTimeWinner != "Tie" {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", firstHalfWinner)
}
switch {
@ -721,19 +749,19 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
}
switch {
case secondHalfWinner == outcome.HomeTeamName && firstHalfScore.Home < firstHalfScore.Away:
case fullTimeWinner == outcome.HomeTeamName && fullTimeScore.Home < fullTimeScore.Away:
return domain.OUTCOME_STATUS_LOSS, nil
case secondHalfWinner == outcome.AwayTeamName && firstHalfScore.Away < firstHalfScore.Home:
case fullTimeWinner == outcome.AwayTeamName && fullTimeScore.Away < fullTimeScore.Home:
return domain.OUTCOME_STATUS_LOSS, nil
case secondHalfWinner == "Tie" && firstHalfScore.Home != firstHalfScore.Away:
case fullTimeWinner == "Tie" && fullTimeScore.Home != fullTimeScore.Away:
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_WIN, nil
}
}
// Highest Scoring Half betting is a type of bet where the bettor predicts which half of the match will have the highest total score.
func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Highest Scoring Half betting is a type of bet where the bettor predicts which half of the match will have the highest total score.
func evaluateHighestScoringHalf(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
firstHalfTotal := firstScore.Home + firstScore.Away
secondHalfTotal := secondScore.Home + secondScore.Away
switch outcome.OddName {
@ -753,10 +781,10 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
return domain.OUTCOME_STATUS_LOSS, nil
}
}
// Highest Scoring Quarter betting is a type of bet where the bettor predicts which quarter of the match will have the highest score.
func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Highest Scoring Quarter betting is a type of bet where the bettor predicts which quarter of the match will have the highest score.
func evaluateHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
firstQuarterTotal := firstScore.Home + firstScore.Away
secondQuarterTotal := secondScore.Home + secondScore.Away
thirdQuarterTotal := thirdScore.Home + thirdScore.Away
@ -787,10 +815,10 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
return domain.OUTCOME_STATUS_LOSS, nil
}
}
// Team With Highest Scoring Quarter betting is a type of bet where the bettor predicts which team will have the highest score in a specific quarter.
func evaluateTeamWithHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Team With Highest Scoring Quarter betting is a type of bet where the bettor predicts which team will have the highest score in a specific quarter.
func evaluateTeamWithHighestScoringQuarter(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }, fourthScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
homeTeamHighestQuarter := max(firstScore.Home, secondScore.Home, thirdScore.Home, fourthScore.Home)
awayTeamHighestQuarter := max(firstScore.Away, secondScore.Away, thirdScore.Away, fourthScore.Away)
@ -811,11 +839,11 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
return domain.OUTCOME_STATUS_LOSS, nil
}
}
// Handicap and Total betting is a combination of spread betting and total points betting
// where the bettor predicts the outcome of a match with a point spread and the total number of points scored is over or under a specified number.
func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Handicap and Total betting is a combination of spread betting and total points betting
// where the bettor predicts the outcome of a match with a point spread and the total number of points scored is over or under a specified number.
func evaluateHandicapAndTotal(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
nameSplit := strings.Split(outcome.OddName, " ")
// Evaluate from bottom to get the threshold and find out if its over or under
@ -843,10 +871,12 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
}
teamName := strings.TrimSpace(strings.Join(nameSplit[:len(nameSplit)-4], ""))
adjustedHomeScore := float64(score.Home)
adjustedAwayScore := float64(score.Away)
outcome.HomeTeamName = strings.Join(strings.Split(outcome.HomeTeamName, " "), "")
outcome.AwayTeamName = strings.Join(strings.Split(outcome.AwayTeamName, " "), "")
switch teamName {
case outcome.HomeTeamName:
adjustedHomeScore += handicap
@ -864,25 +894,25 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("failed parsing team name: %s", outcome.OddName)
}
}
}
// Winning Margin betting is a type of bet where the bettor predicts the margin of victory in a match.
func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
marginSplit := strings.Split(outcome.OddName, "")
if len(marginSplit) < 1 {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
margin, err := strconv.ParseInt(marginSplit[0], 10, 64)
if err != nil {
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
func evaluateWinningMargin(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
if len(outcome.OddName) < 1 {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
isGtr := false
if len(marginSplit) == 2 {
isGtr = marginSplit[1] == "+"
idx := len(outcome.OddName)
if outcome.OddName[idx-1] == '+' {
isGtr = true
idx--
}
margin, err := strconv.ParseInt(outcome.OddName[:idx], 10, 64)
if err != nil {
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
switch outcome.OddHeader {
case "1":
if score.Home == (score.Away + int(margin)) {
@ -892,19 +922,19 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
}
return domain.OUTCOME_STATUS_LOSS, nil
case "2":
if (score.Home + int(margin)) == score.Away {
if score.Away == (score.Home + int(margin)) {
return domain.OUTCOME_STATUS_WIN, nil
} else if isGtr && (score.Home+int(margin)) > score.Away {
} else if isGtr && score.Away > (score.Home+int(margin)) {
return domain.OUTCOME_STATUS_WIN, nil
}
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddheader: %s", outcome.OddHeader)
}
}
// Highest Scoring Period betting is a type of bet where the bettor predicts which period of the match will have the highest total score.
func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Highest Scoring Period betting is a type of bet where the bettor predicts which period of the match will have the highest total score.
func evaluateHighestScoringPeriod(outcome domain.BetOutcome, firstScore struct{ Home, Away int }, secondScore struct{ Home, Away int }, thirdScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
firstPeriodTotal := firstScore.Home + firstScore.Away
secondPeriodTotal := secondScore.Home + secondScore.Away
thirdPeriodTotal := thirdScore.Home + thirdScore.Away
@ -930,10 +960,10 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
return domain.OUTCOME_STATUS_LOSS, nil
}
}
// Tied After Regulation is a type of bet where the bettor predicts whether the match will end in a tie after regulation time.
func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) {
// Tied After Regulation is a type of bet where the bettor predicts whether the match will end in a tie after regulation time.
func evaluateTiedAfterRegulation(outcome domain.BetOutcome, scores []struct{ Home, Away int }) (domain.OutcomeStatus, error) {
totalScore := struct{ Home, Away int }{0, 0}
for _, score := range scores {
totalScore.Home += score.Home
@ -952,11 +982,10 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
return domain.OUTCOME_STATUS_LOSS, nil
}
return domain.OUTCOME_STATUS_ERROR, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid oddname: %s", outcome.OddName)
}
// evaluateRugbyOutcome evaluates the outcome of a Rugby bet
func evaluateRugbyOutcome(outcome domain.BetOutcome, result *domain.RugbyResultResponse) (domain.OutcomeStatus, error) {
func evaluateRugbyOutcome(outcome domain.BetOutcome, result *domain.RugbyResultResponse) (domain.OutcomeStatus, error) {
finalScore := parseSS(result.SS)
switch outcome.MarketName {
@ -969,10 +998,9 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported rugby market: %s", outcome.MarketName)
}
}
}
// evaluateBaseballOutcome evaluates the outcome of a Baseball bet
func evaluateBaseballOutcome(outcome domain.BetOutcome, result *domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
func evaluateBaseballOutcome(outcome domain.BetOutcome, result *domain.BaseballResultResponse) (domain.OutcomeStatus, error) {
finalScore := parseSS(result.SS)
switch outcome.MarketName {
@ -985,4 +1013,13 @@ func evaluateTotalOverUnder(outcome domain.BetOutcome, score struct{ Home, Away
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported baseball market: %s", outcome.MarketName)
}
}
func evaluateVolleyballGamelines(outcome domain.BetOutcome, score struct{ Home, Away int }) (domain.OutcomeStatus, error) {
switch outcome.OddName {
case "Total":
return evaluateTotalOverUnder(outcome, score)
default:
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("invalid odd name: %s", outcome.OddName)
}
}

View File

@ -227,7 +227,9 @@ func (s *Service) FetchAndProcessResults(ctx context.Context) error {
// }
func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, sportID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID)
// url := fmt.Sprintf("https://api.b365api.com/v1/bet365/result?token=%s&event_id=%d", s.config.Bet365Token, eventID)
url := fmt.Sprintf("https://api.b365api.com/v1/event/view?token=%s&event_id=%d", s.config.Bet365Token, eventID)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
s.logger.Error("Failed to create request", "event_id", eventID, "error", err)
@ -261,18 +263,70 @@ func (s *Service) fetchResult(ctx context.Context, eventID, oddID, marketID, spo
switch sportID {
case domain.FOOTBALL:
result, err = s.parseFootball(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse football", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.BASKETBALL:
result, err = s.parseBasketball(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse basketball", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.ICE_HOCKEY:
result, err = s.parseIceHockey(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse ice hockey", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.CRICKET:
result, err = s.parseCricket(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse cricket", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.VOLLEYBALL:
result, err = s.parseVolleyball(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse volleyball", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.DARTS:
result, err = s.parseDarts(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse darts", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.FUTSAL:
result, err = s.parseFutsal(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse futsal", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.AMERICAN_FOOTBALL:
result, err = s.parseNFL(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse american football", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.RUGBY_UNION:
result, err = s.parseRugbyUnion(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse rugby_union", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.RUGBY_LEAGUE:
result, err = s.parseRugbyLeague(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse rugby_league", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
case domain.BASEBALL:
result, err = s.parseBaseball(resultResp.Results[0], eventID, oddID, marketID, outcome)
if err != nil {
s.logger.Error("Failed to parse baseball", "event id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
default:
s.logger.Error("Unsupported sport", "sport", sportID)
return domain.CreateResult{}, fmt.Errorf("unsupported sport: %v", sportID)
@ -352,6 +406,7 @@ func (s *Service) parseFootball(resultRes json.RawMessage, eventID, oddID, marke
func (s *Service) parseBasketball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var basketBallRes domain.BasketballResultResponse
if err := json.Unmarshal(response, &basketBallRes); err != nil {
s.logger.Error("Failed to unmarshal basketball result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
@ -416,6 +471,121 @@ func (s *Service) parseIceHockey(response json.RawMessage, eventID, oddID, marke
}
func (s *Service) parseCricket(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var cricketRes domain.CricketResultResponse
if err := json.Unmarshal(response, &cricketRes); err != nil {
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if cricketRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateCricketOutcome(outcome, cricketRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
return domain.CreateResult{
BetOutcomeID: 0,
EventID: eventID,
OddID: oddID,
MarketID: marketID,
Status: status,
}, nil
}
func (s *Service) parseVolleyball(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var volleyballRes domain.VolleyballResultResponse
if err := json.Unmarshal(response, &volleyballRes); err != nil {
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if volleyballRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateVolleyballOutcome(outcome, volleyballRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
return domain.CreateResult{
BetOutcomeID: 0,
EventID: eventID,
OddID: oddID,
MarketID: marketID,
Status: status,
}, nil
}
func (s *Service) parseDarts(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var dartsRes domain.DartsResultResponse
if err := json.Unmarshal(response, &dartsRes); err != nil {
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if dartsRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
// result for dart is limited
// only ss is given, format with 2-4
status, err := s.evaluateDartsOutcome(outcome, dartsRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
return domain.CreateResult{
BetOutcomeID: 0,
EventID: eventID,
OddID: oddID,
MarketID: marketID,
Status: status,
}, nil
}
func (s *Service) parseFutsal(response json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var futsalRes domain.FutsalResultResponse
if err := json.Unmarshal(response, &futsalRes); err != nil {
s.logger.Error("Failed to unmarshal football result", "event_id", eventID, "error", err)
return domain.CreateResult{}, err
}
if futsalRes.TimeStatus != "3" {
s.logger.Warn("Match not yet completed", "event_id", eventID)
return domain.CreateResult{}, fmt.Errorf("match not yet completed")
}
status, err := s.evaluateFutsalOutcome(outcome, futsalRes)
if err != nil {
s.logger.Error("Failed to evaluate outcome", "event_id", eventID, "market_id", marketID, "error", err)
return domain.CreateResult{}, err
}
return domain.CreateResult{
BetOutcomeID: 0,
EventID: eventID,
OddID: oddID,
MarketID: marketID,
Status: status,
}, nil
}
func (s *Service) parseNFL(resultRes json.RawMessage, eventID, oddID, marketID int64, outcome domain.BetOutcome) (domain.CreateResult, error) {
var nflResp domain.NFLResultResponse
if err := json.Unmarshal(resultRes, &nflResp); err != nil {
@ -730,6 +900,92 @@ func (s *Service) evaluateIceHockeyOutcome(outcome domain.BetOutcome, res domain
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateCricketOutcome(outcome domain.BetOutcome, res domain.CricketResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
score := parseSS(res.SS)
switch outcome.MarketID {
case int64(domain.CRICKET_TO_WIN_THE_MATCH):
return evaluateFullTimeResult(outcome, score)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateVolleyballOutcome(outcome domain.BetOutcome, res domain.VolleyballResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
score := parseSS(res.SS)
// res.SS example: { 2-3 } is the win count not actuall score of sets
// for total score we need every set's score
firstSet := parseScore(res.Scores.FirstSet.Home, res.Scores.FirstSet.Away)
secondSet := parseScore(res.Scores.SecondSet.Home, res.Scores.SecondSet.Away)
thirdSet := parseScore(res.Scores.ThirdSet.Home, res.Scores.ThirdSet.Away)
fourthSet := parseScore(res.Scores.FourthSet.Home, res.Scores.FourthSet.Away)
fivethSet := parseScore(res.Scores.FivethSet.Home, res.Scores.FivethSet.Away)
totalScore := struct{ Home, Away int }{Home: 0, Away: 0}
totalScore.Home = firstSet.Home + secondSet.Home + thirdSet.Home + fourthSet.Home + fivethSet.Home
totalScore.Away = firstSet.Away + secondSet.Away + thirdSet.Away + fourthSet.Away + fivethSet.Away
switch outcome.MarketID {
case int64(domain.VOLLEYBALL_GAME_LINES):
return evaluateVolleyballGamelines(outcome, totalScore)
case int64(domain.VOLLEYBALL_MATCH_TOTAL_ODD_EVEN):
return evaluateGoalsOddEven(outcome, totalScore)
case int64(domain.VOLLEYBALL_CORRECT_SET_SCORE):
return evaluateCorrectScore(outcome, score)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateDartsOutcome(outcome domain.BetOutcome, res domain.DartsResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
score := parseSS(res.SS)
switch outcome.MarketID {
case int64(domain.DARTS_MATCH_WINNER):
return evaluateFullTimeResult(outcome, score)
case int64(domain.DARTS_TOTAL_LEGS):
return evaluateTotalLegs(outcome, score)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateFutsalOutcome(outcome domain.BetOutcome, res domain.FutsalResultResponse) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)
return domain.OUTCOME_STATUS_PENDING, fmt.Errorf("unsupported market type: %s", outcome.MarketName)
}
score := parseSS(res.SS)
switch outcome.MarketID {
case int64(domain.FUTSAL_GAME_LINES):
return evaluateGameLines(outcome, score)
case int64(domain.FUTSAL_MONEY_LINE):
return evaluateMoneyLine(outcome, score)
case int64(domain.FUTSAL_TEAM_TO_SCORE_FIRST):
return evaluateFirstTeamToScore(outcome, res.Events)
}
return domain.OUTCOME_STATUS_PENDING, nil
}
func (s *Service) evaluateNFLOutcome(outcome domain.BetOutcome, finalScore struct{ Home, Away int }) (domain.OutcomeStatus, error) {
if !domain.SupportedMarkets[outcome.MarketID] {
s.logger.Warn("Unsupported market type", "market_name", outcome.MarketName)

View File

@ -1,49 +1 @@
package result
import (
"fmt"
"testing"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
)
func TestEvaluateFootballOutcome(t *testing.T) {
service := &Service{} // or your real logger
// Mock outcome
outcome := domain.BetOutcome{
ID: 1,
BetID: 1,
EventID: 1001,
OddID: 2001,
SportID: 1, // Assuming 1 = Football
HomeTeamName: "Manchester",
AwayTeamName: "Liverpool",
MarketID: int64(domain.FOOTBALL_FULL_TIME_RESULT),
MarketName: "Full Time Result",
Odd: 1.75,
OddName: "2", // Home win
OddHeader: "1",
OddHandicap: "",
Status: domain.OUTCOME_STATUS_PENDING, // Initial status
Expires: time.Now().Add(24 * time.Hour),
}
// Parsed result (simulate Bet365 JSON)
finalScore := struct{ Home, Away int }{Home: 2, Away: 1}
firstHalfScore := struct{ Home, Away int }{Home: 1, Away: 1}
secondHalfScore := struct{ Home, Away int }{Home: 1, Away: 0}
corners := struct{ Home, Away int }{Home: 5, Away: 3}
halfTimeCorners := struct{ Home, Away int }{Home: 2, Away: 2}
events := []map[string]string{
{"type": "goal", "team": "home", "minute": "23"},
{"type": "goal", "team": "away", "minute": "34"},
}
// Act
status, _ := service.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, events)
fmt.Printf("\n\nBet Outcome: %v\n\n", &status)
}

View File

@ -1,7 +1,9 @@
package result
import (
"fmt"
"testing"
"time"
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
"github.com/stretchr/testify/assert"
@ -301,3 +303,678 @@ func TestBaseballMarkets(t *testing.T) {
})
}
}
func TestEvaluateFootballOutcome(t *testing.T) {
service := &Service{} // or your real logger
// Mock outcome
outcome := domain.BetOutcome{
ID: 1,
BetID: 1,
EventID: 1001,
OddID: 2001,
SportID: 1, // Assuming 1 = Football
HomeTeamName: "Manchester",
AwayTeamName: "Liverpool",
MarketID: int64(domain.FOOTBALL_FULL_TIME_RESULT),
MarketName: "Full Time Result",
Odd: 1.75,
OddName: "2", // Home win
OddHeader: "1",
OddHandicap: "",
Status: domain.OUTCOME_STATUS_PENDING, // Initial status
Expires: time.Now().Add(24 * time.Hour),
}
// Parsed result (simulate Bet365 JSON)
finalScore := struct{ Home, Away int }{Home: 2, Away: 1}
firstHalfScore := struct{ Home, Away int }{Home: 1, Away: 1}
secondHalfScore := struct{ Home, Away int }{Home: 1, Away: 0}
corners := struct{ Home, Away int }{Home: 5, Away: 3}
halfTimeCorners := struct{ Home, Away int }{Home: 2, Away: 2}
events := []map[string]string{
{"type": "goal", "team": "home", "minute": "23"},
{"type": "goal", "team": "away", "minute": "34"},
}
// Act
status, _ := service.evaluateFootballOutcome(outcome, finalScore, firstHalfScore, secondHalfScore, corners, halfTimeCorners, events)
fmt.Printf("\n\nBet Outcome: %v\n\n", &status)
}
func TestEvaluateTotalLegs(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"OverTotalLegs", domain.BetOutcome{OddName: "3", OddHeader: "Over"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_WIN},
{"OverTotalLegs", domain.BetOutcome{OddName: "3", OddHeader: "Under"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_LOSS},
{"UnderTotalLegs", domain.BetOutcome{OddName: "7", OddHeader: "Under"}, struct{ Home, Away int }{2, 3}, domain.OUTCOME_STATUS_WIN},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateTotalLegs(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateGameLines(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"GameLines - Total", domain.BetOutcome{OddName: "Total", OddHandicap: "O 5.5"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_WIN},
{"GameLines - Total", domain.BetOutcome{OddName: "Total", OddHandicap: "O 5.5"}, struct{ Home, Away int }{2, 3}, domain.OUTCOME_STATUS_LOSS},
{"GameLines - Money Line", domain.BetOutcome{OddName: "Money Line", OddHeader: "1"}, struct{ Home, Away int }{2, 3}, domain.OUTCOME_STATUS_LOSS},
{"GameLines - Money Line", domain.BetOutcome{OddName: "Money Line", OddHeader: "1"}, struct{ Home, Away int }{3, 2}, domain.OUTCOME_STATUS_WIN},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateGameLines(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateFirstTeamToScore(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
events []map[string]string
expected domain.OutcomeStatus
}{
{"HomeScoreFirst", domain.BetOutcome{OddName: "1", HomeTeamName: "Team A", AwayTeamName: "Team B"}, []map[string]string{
{"text": "1st Goal - Team A"},
}, domain.OUTCOME_STATUS_WIN},
{"AwayScoreFirst", domain.BetOutcome{OddName: "2", HomeTeamName: "Team A", AwayTeamName: "Team B"}, []map[string]string{
{"text": "1st Goal - Team A"},
}, domain.OUTCOME_STATUS_LOSS},
{"AwayScoreFirst", domain.BetOutcome{OddName: "2", HomeTeamName: "Team A", AwayTeamName: "Team B"}, []map[string]string{
{"text": "1st Goal - Team B"},
}, domain.OUTCOME_STATUS_WIN},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateFirstTeamToScore(tt.outcome, tt.events)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateGoalsOverUnder(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"LosingGoalsOver", domain.BetOutcome{OddHeader: "Over", OddName: "13"}, struct{ Home, Away int }{7, 5}, domain.OUTCOME_STATUS_LOSS},
{"WinningGoalsOver", domain.BetOutcome{OddHeader: "Over", OddName: "11"}, struct{ Home, Away int }{7, 5}, domain.OUTCOME_STATUS_WIN},
{"WinningGoalsUnder", domain.BetOutcome{OddHeader: "Under", OddName: "12"}, struct{ Home, Away int }{6, 5}, domain.OUTCOME_STATUS_WIN},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateGoalsOverUnder(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateGoalsOddEven(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningOddGoals", domain.BetOutcome{OddName: "Odd"}, struct{ Home, Away int }{7, 4}, domain.OUTCOME_STATUS_WIN},
{"LosingEvenGoals", domain.BetOutcome{OddName: "Even"}, struct{ Home, Away int }{7, 4}, domain.OUTCOME_STATUS_LOSS},
{"WinningEvenGoals", domain.BetOutcome{OddName: "Even"}, struct{ Home, Away int }{6, 6}, domain.OUTCOME_STATUS_WIN},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateGoalsOddEven(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateCorrectScore(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"CorrectScore", domain.BetOutcome{OddName: "7-4"}, struct{ Home, Away int }{7, 4}, domain.OUTCOME_STATUS_WIN},
{"CorrectScore", domain.BetOutcome{OddName: "6-6"}, struct{ Home, Away int }{6, 6}, domain.OUTCOME_STATUS_WIN},
{"IncorrectScore", domain.BetOutcome{OddName: "2-3"}, struct{ Home, Away int }{7, 4}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateCorrectScore(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateHighestScoringHalf(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
firstScore struct{ Home, Away int }
secondScore struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"Winning1stHalf", domain.BetOutcome{OddName: "1st Half"}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{0, 0}, domain.OUTCOME_STATUS_WIN},
{"Losing1stHalf", domain.BetOutcome{OddName: "1st Half"}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{2, 2}, domain.OUTCOME_STATUS_LOSS},
{"Losing2ndHalf", domain.BetOutcome{OddName: "2nd Half"}, struct{ Home, Away int }{0, 0}, struct{ Home, Away int }{0, 0}, domain.OUTCOME_STATUS_LOSS},
{"Winning2ndHalf", domain.BetOutcome{OddName: "2nd Half"}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{2, 2}, domain.OUTCOME_STATUS_WIN},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateHighestScoringHalf(tt.outcome, tt.firstScore, tt.secondScore)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateHighestScoringQuarter(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
firstScore struct{ Home, Away int }
secondScore struct{ Home, Away int }
thirdScore struct{ Home, Away int }
fourthScore struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"Winning1stQuarter", domain.BetOutcome{OddName: "1st Quarter"},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{0, 0},
struct{ Home, Away int }{1, 0},
struct{ Home, Away int }{0, 0},
domain.OUTCOME_STATUS_WIN},
{"Losing1stQuarter", domain.BetOutcome{OddName: "1st Quarter"},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{0, 0},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{0, 0},
domain.OUTCOME_STATUS_LOSS},
{"Losing2ndQuarter", domain.BetOutcome{OddName: "2nd Quarter"},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{0, 0},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{0, 0},
domain.OUTCOME_STATUS_LOSS},
{"Winning3rdQuarter", domain.BetOutcome{OddName: "3rd Quarter"},
struct{ Home, Away int }{1, 0},
struct{ Home, Away int }{0, 0},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{0, 0},
domain.OUTCOME_STATUS_WIN},
{"Wining4thQuarter", domain.BetOutcome{OddName: "4th Quarter"},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{0, 0},
struct{ Home, Away int }{1, 1},
struct{ Home, Away int }{2, 2},
domain.OUTCOME_STATUS_WIN},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateHighestScoringQuarter(tt.outcome, tt.firstScore, tt.secondScore, tt.thirdScore, tt.fourthScore)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateWinningMargin(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningMargin", domain.BetOutcome{OddHeader: "1", OddName: "12"}, struct{ Home, Away int }{12, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningMargin", domain.BetOutcome{OddHeader: "2", OddName: "3"}, struct{ Home, Away int }{1, 4}, domain.OUTCOME_STATUS_WIN},
{"WinningMargin", domain.BetOutcome{OddHeader: "1", OddName: "3+"}, struct{ Home, Away int }{4, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningMargin", domain.BetOutcome{OddHeader: "2", OddName: "12+"}, struct{ Home, Away int }{0, 13}, domain.OUTCOME_STATUS_WIN},
{"LosingMargin", domain.BetOutcome{OddHeader: "2", OddName: "3"}, struct{ Home, Away int }{0, 4}, domain.OUTCOME_STATUS_LOSS},
{"LosingMargin", domain.BetOutcome{OddHeader: "2", OddName: "3+"}, struct{ Home, Away int }{1, 3}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateWinningMargin(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateDoubleResult(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
firstHalfScore struct{ Home, Away int }
fullTimeScore struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHomeAway", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A - Team B"}, struct{ Home, Away int }{1, 0}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
{"WinningAwayHome", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team B - Team A"}, struct{ Home, Away int }{0, 1}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_WIN},
{"WinningTie", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Tie - Tie"}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{2, 2}, domain.OUTCOME_STATUS_WIN},
{"WinningTieAway", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Tie - Team B"}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
{"LosingHomeAway", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A - Team B"}, struct{ Home, Away int }{1, 0}, struct{ Home, Away int }{2, 0}, domain.OUTCOME_STATUS_LOSS},
{"LosingTie", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Tie - Tie"}, struct{ Home, Away int }{1, 0}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_LOSS},
{"LosingTieAway", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Tie - Team A"}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_LOSS},
{"BadInput", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A - "}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_PENDING},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateDoubleResult(tt.outcome, tt.firstHalfScore, tt.fullTimeScore)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateHighestScoringPeriod(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
firstScore struct{ Home, Away int }
secondScore struct{ Home, Away int }
thirdScore struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"Winning1stPeriod", domain.BetOutcome{OddName: "Period 1"}, struct{ Home, Away int }{2, 2}, struct{ Home, Away int }{1, 1}, struct{ Home, Away int }{0, 0}, domain.OUTCOME_STATUS_WIN},
{"Winning2ndPeriod", domain.BetOutcome{OddName: "Period 2"}, struct{ Home, Away int }{2, 2}, struct{ Home, Away int }{2, 3}, struct{ Home, Away int }{0, 0}, domain.OUTCOME_STATUS_WIN},
{"Winning3rdPeriod", domain.BetOutcome{OddName: "Period 3"}, struct{ Home, Away int }{2, 2}, struct{ Home, Away int }{2, 3}, struct{ Home, Away int }{3, 3}, domain.OUTCOME_STATUS_WIN},
{"WinningTie", domain.BetOutcome{OddName: "Tie"}, struct{ Home, Away int }{2, 2}, struct{ Home, Away int }{2, 2}, struct{ Home, Away int }{0, 0}, domain.OUTCOME_STATUS_WIN},
{"Losing1stPeriod", domain.BetOutcome{OddName: "Period 1"}, struct{ Home, Away int }{2, 2}, struct{ Home, Away int }{2, 3}, struct{ Home, Away int }{0, 0}, domain.OUTCOME_STATUS_LOSS},
{"Losing3rdPeriod", domain.BetOutcome{OddName: "Period 3"}, struct{ Home, Away int }{2, 2}, struct{ Home, Away int }{2, 3}, struct{ Home, Away int }{0, 0}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateHighestScoringPeriod(tt.outcome, tt.firstScore, tt.secondScore, tt.thirdScore)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvalauteTiedAfterRegulation(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score []struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningTied", domain.BetOutcome{OddName: "Yes"}, []struct{ Home, Away int }{{1, 0}, {0, 1}, {2, 2}}, domain.OUTCOME_STATUS_WIN},
{"WinningTied", domain.BetOutcome{OddName: "Yes"}, []struct{ Home, Away int }{{1, 1}, {0, 1}, {2, 2}, {2, 1}}, domain.OUTCOME_STATUS_WIN},
{"WinningNotTied", domain.BetOutcome{OddName: "No"}, []struct{ Home, Away int }{{0, 0}, {0, 0}, {0, 0}, {1, 0}}, domain.OUTCOME_STATUS_WIN},
{"LosingTied", domain.BetOutcome{OddName: "Yes"}, []struct{ Home, Away int }{{0, 2}, {0, 0}, {0, 0}, {0, 0}}, domain.OUTCOME_STATUS_LOSS},
{"LosingNotTied", domain.BetOutcome{OddName: "No"}, []struct{ Home, Away int }{{0, 0}, {0, 0}, {0, 0}, {0, 0}}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateTiedAfterRegulation(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateTeamTotal(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHomeUnder", domain.BetOutcome{OddHandicap: "Under 3", OddHeader: "1"}, struct{ Home, Away int }{2, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningHomeOver", domain.BetOutcome{OddHandicap: "Over 2", OddHeader: "1"}, struct{ Home, Away int }{3, 1}, domain.OUTCOME_STATUS_WIN},
{"WinningAwayOver", domain.BetOutcome{OddHandicap: "Over 2", OddHeader: "2"}, struct{ Home, Away int }{1, 3}, domain.OUTCOME_STATUS_WIN},
{"LosingHomeOver", domain.BetOutcome{OddHandicap: "Over 2", OddHeader: "1"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_LOSS},
{"LosingAwayOver", domain.BetOutcome{OddHandicap: "Over 2", OddHeader: "2"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateTeamTotal(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestDrawNoBet(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHome", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningAway", domain.BetOutcome{OddName: "2"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
{"LosingHome", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_LOSS},
{"Tie", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_VOID},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateDrawNoBet(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateMoneyLine(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHome", domain.BetOutcome{OddHeader: "1"}, struct{ Home, Away int }{1, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningAway", domain.BetOutcome{OddHeader: "2"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
{"WinningTie", domain.BetOutcome{OddHeader: "Tie"}, struct{ Home, Away int }{2, 2}, domain.OUTCOME_STATUS_WIN},
{"LosingTie", domain.BetOutcome{OddHeader: "1"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_LOSS},
{"LosingAway", domain.BetOutcome{OddHeader: "2"}, struct{ Home, Away int }{3, 2}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateMoneyLine(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateDoubleChance(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHomeOrDraw", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "1 or Draw"}, struct{ Home, Away int }{1, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningHomeOrDraw", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A or Draw"}, struct{ Home, Away int }{1, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningAwayOrDraw", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Draw or Team B"}, struct{ Home, Away int }{0, 1}, domain.OUTCOME_STATUS_WIN},
{"LosingHomeorAway", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "1 or 2"}, struct{ Home, Away int }{1, 1}, domain.OUTCOME_STATUS_LOSS},
{"LosingAwayOrDraw", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Draw or 2"}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateDoubleChance(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateResultAndTotal(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHomeOver", domain.BetOutcome{OddHeader: "1", OddHandicap: "Over 4"}, struct{ Home, Away int }{3, 2}, domain.OUTCOME_STATUS_WIN},
{"WinningHomeUnder", domain.BetOutcome{OddHeader: "1", OddHandicap: "Under 4"}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_WIN},
{"WinningAwayUnder", domain.BetOutcome{OddHeader: "2", OddHandicap: "Under 4"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
{"LosingHomeOver", domain.BetOutcome{OddHeader: "1", OddHandicap: "Under 4"}, struct{ Home, Away int }{3, 2}, domain.OUTCOME_STATUS_LOSS},
{"LosingAwayUnder", domain.BetOutcome{OddHeader: "2", OddHandicap: "Under 4"}, struct{ Home, Away int }{2, 2}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateResultAndTotal(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestCheckMultiOutcome(t *testing.T) {
tests := []struct {
name string
outcome domain.OutcomeStatus
secondOutcome domain.OutcomeStatus
expected domain.OutcomeStatus
}{
{"Win-Win", domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_WIN},
{"Win-Void", domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_VOID, domain.OUTCOME_STATUS_HALF},
{"Win-Loss", domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_LOSS, domain.OUTCOME_STATUS_LOSS},
{"Loss-Loss", domain.OUTCOME_STATUS_LOSS, domain.OUTCOME_STATUS_LOSS, domain.OUTCOME_STATUS_LOSS},
{"Loss-Void", domain.OUTCOME_STATUS_LOSS, domain.OUTCOME_STATUS_VOID, domain.OUTCOME_STATUS_VOID},
{"Loss-Win", domain.OUTCOME_STATUS_LOSS, domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_LOSS},
{"Void-Win", domain.OUTCOME_STATUS_VOID, domain.OUTCOME_STATUS_WIN, domain.OUTCOME_STATUS_VOID},
{"Void-Loss", domain.OUTCOME_STATUS_VOID, domain.OUTCOME_STATUS_LOSS, domain.OUTCOME_STATUS_VOID},
{"Void-Void", domain.OUTCOME_STATUS_VOID, domain.OUTCOME_STATUS_VOID, domain.OUTCOME_STATUS_VOID},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := checkMultiOutcome(tt.outcome, tt.secondOutcome)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateBTTSX(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningBothScoreX", domain.BetOutcome{OddName: "3", OddHeader: "Yes"}, struct{ Home, Away int }{3, 4}, domain.OUTCOME_STATUS_WIN},
{"WinningBothScoreLess", domain.BetOutcome{OddName: "3", OddHeader: "No"}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_WIN},
{"LosingBothScoreX", domain.BetOutcome{OddName: "3", OddHeader: "Yes"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_LOSS},
{"LosingBothScoreLess", domain.BetOutcome{OddName: "3", OddHeader: "No"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateBTTSX(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateResultAndBTTSX(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHomeAndBothScoreX", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A and Yes", OddHeader: "3"}, struct{ Home, Away int }{4, 3}, domain.OUTCOME_STATUS_WIN},
{"WinningHomeAndBothScoreLess", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A and No", OddHeader: "3"}, struct{ Home, Away int }{2, 1}, domain.OUTCOME_STATUS_WIN},
{"WinningAwayAndBothScoreX", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team B and Yes", OddHeader: "3"}, struct{ Home, Away int }{3, 4}, domain.OUTCOME_STATUS_WIN},
{"WinningAwayAndBothScoreLess", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team B and No", OddHeader: "3"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
{"LosingHomeAndBothScoreX", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A and Yes", OddHeader: "3"}, struct{ Home, Away int }{3, 4}, domain.OUTCOME_STATUS_LOSS},
{"LosingHomeAndBothScoreX", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team B and Yes", OddHeader: "3"}, struct{ Home, Away int }{4, 2}, domain.OUTCOME_STATUS_LOSS},
{"LosingAwayAndBothScoreX", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team B and Yes", OddHeader: "3"}, struct{ Home, Away int }{2, 4}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateResultAndBTTSX(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateMoneyLine3Way(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"WinningHome", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 0}, domain.OUTCOME_STATUS_WIN},
{"WinningAway", domain.BetOutcome{OddName: "2"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_WIN},
{"WinningTie", domain.BetOutcome{OddName: "Tie"}, struct{ Home, Away int }{2, 2}, domain.OUTCOME_STATUS_WIN},
{"LosingTie", domain.BetOutcome{OddName: "1"}, struct{ Home, Away int }{1, 2}, domain.OUTCOME_STATUS_LOSS},
{"LosingAway", domain.BetOutcome{OddName: "2"}, struct{ Home, Away int }{3, 2}, domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateMoneyLine3Way(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateAsianHandicap(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{
name: "Home -1 Win",
outcome: domain.BetOutcome{OddHeader: "1", OddHandicap: "-1"},
score: struct{ Home, Away int }{Home: 2, Away: 0},
expected: domain.OUTCOME_STATUS_WIN,
},
{
name: "Home -0.5 Win",
outcome: domain.BetOutcome{OddHeader: "1", OddHandicap: "-0.5"},
score: struct{ Home, Away int }{Home: 1, Away: 0},
expected: domain.OUTCOME_STATUS_WIN,
},
{
name: "Home -1 Void",
outcome: domain.BetOutcome{OddHeader: "1", OddHandicap: "-1"},
score: struct{ Home, Away int }{Home: 1, Away: 0},
expected: domain.OUTCOME_STATUS_VOID,
},
{
name: "Away +3 Win",
outcome: domain.BetOutcome{OddHeader: "2", OddHandicap: "3"},
score: struct{ Home, Away int }{Home: 1, Away: 2},
expected: domain.OUTCOME_STATUS_WIN,
},
{
name: "Split Handicap Home -0.5,-1 Win/Win",
outcome: domain.BetOutcome{OddHeader: "1", OddHandicap: "-0.5,-1"},
score: struct{ Home, Away int }{Home: 2, Away: 0},
expected: domain.OUTCOME_STATUS_WIN,
},
{
name: "Split Handicap Home -0.5,-1 Win/Void",
outcome: domain.BetOutcome{OddHeader: "1", OddHandicap: "-0.5,-1"},
score: struct{ Home, Away int }{Home: 1, Away: 0},
expected: domain.OUTCOME_STATUS_WIN,
},
{
name: "Invalid Handicap",
outcome: domain.BetOutcome{OddHeader: "1", OddHandicap: "invalid"},
score: struct{ Home, Away int }{Home: 1, Away: 0},
expected: domain.OUTCOME_STATUS_ERROR,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateAsianHandicap(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}
func TestEvaluateHandicapAndTotal(t *testing.T) {
tests := []struct {
name string
outcome domain.BetOutcome
score struct{ Home, Away int }
expected domain.OutcomeStatus
}{
{"Home +2.5 Over 3", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A +2.5 & Over 3"},
struct{ Home, Away int }{4, 0},
domain.OUTCOME_STATUS_WIN},
{"Away +2.5 Over 4", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team B +2.5 & Over 4"},
struct{ Home, Away int }{1, 5},
domain.OUTCOME_STATUS_WIN},
{"Home +2.5 Over 3", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A +2.5 & Over 3"},
struct{ Home, Away int }{2, 0},
domain.OUTCOME_STATUS_LOSS},
{"Home -3.5 Over 3", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team A -2.5 & Over 3"},
struct{ Home, Away int }{4, 3},
domain.OUTCOME_STATUS_LOSS},
{"Away -3 Over 4", domain.BetOutcome{HomeTeamName: "Team A", AwayTeamName: "Team B", OddName: "Team B -3 & Over 4"},
struct{ Home, Away int }{3, 5},
domain.OUTCOME_STATUS_LOSS},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
status, _ := evaluateHandicapAndTotal(tt.outcome, tt.score)
if status != tt.expected {
t.Errorf("expected %d, got %d", tt.expected, status)
}
})
}
}

View File

@ -39,6 +39,11 @@ migrations/up:
@echo 'Running up migrations...'
@docker compose up migrate
.PHONY: postgres
postgres:
@echo 'Running postgres db...'
docker compose -f docker-compose.yml exec postgres psql -U root -d gh
.PHONY: swagger
swagger:
@swag init -g cmd/main.go