Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/just_gtfs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Byko-Ianko <bykoianko@gmail.com>2020-05-14 10:41:40 +0300
committerGitHub <noreply@github.com>2020-05-14 10:41:40 +0300
commit9a21d029e8c88a1bf05e0c7e970fed31165d6b65 (patch)
treece6da8858df47a2cee8bb0108f84eab144c7d573
parentf830f4c88ae6c8a273abd505b48ee9101835e51c (diff)
parentbce5517bb003fd421c6ab3e3d0bfac8e69ec6b69 (diff)
Merge pull request #2 from mapsme/add-all-readers
Added 7 readers and extended unit tests
-rw-r--r--.clang-format6
-rw-r--r--include/just_gtfs/just_gtfs.h542
-rw-r--r--tests/data/sample_feed/attributions.txt2
-rw-r--r--tests/data/sample_feed/feed_info.txt2
-rw-r--r--tests/data/sample_feed/levels.txt4
-rw-r--r--tests/data/sample_feed/pathways.txt4
-rw-r--r--tests/data/sample_feed/transfers.txt5
-rw-r--r--tests/data/sample_feed/translations.txt2
-rw-r--r--tests/unit_tests.cpp215
9 files changed, 616 insertions, 166 deletions
diff --git a/.clang-format b/.clang-format
index a82ec7f..adc59d5 100644
--- a/.clang-format
+++ b/.clang-format
@@ -7,9 +7,9 @@ ColumnLimit: 100
Language: Cpp
AccessModifierOffset: -2
-AllowShortBlocksOnASingleLine: false
+AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: true
-AllowShortFunctionsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
BreakConstructorInitializersBeforeComma: true
@@ -19,7 +19,7 @@ IndentCaseLabels: false
NamespaceIndentation: None
PointerAlignment: Middle
SortIncludes: true
-Standard: Cpp11
+Standard: c++17
IncludeBlocks: Preserve
IncludeCategories:
diff --git a/include/just_gtfs/just_gtfs.h b/include/just_gtfs/just_gtfs.h
index 627c020..5a62f09 100644
--- a/include/just_gtfs/just_gtfs.h
+++ b/include/just_gtfs/just_gtfs.h
@@ -19,7 +19,7 @@
namespace gtfs
{
-// Helper classes ----------------------------------------------------------------------------------
+// Helper classes and functions---------------------------------------------------------------------
struct InvalidFieldFormat : public std::exception
{
public:
@@ -50,7 +50,7 @@ struct Result
Message message;
bool operator==(ResultCode result_code) const { return code == result_code; }
- bool operator!=(ResultCode result_code) const { return !(code == result_code); }
+ bool operator!=(ResultCode result_code) const { return !(*this == result_code); }
};
// Csv parser -------------------------------------------------------------------------------------
@@ -574,16 +574,6 @@ enum class PathwayDirection
Bidirectional = 1
};
-enum class TranslationTable
-{
- Agency = 0,
- Stops,
- Routes,
- Trips,
- StopTimes,
- FeedInfo
-};
-
enum class AttributionRole
{
No = 0, // Organization doesn’t have this role
@@ -723,12 +713,12 @@ struct CalendarDate
};
// Optional dataset file
-struct FareAttribute
+struct FareAttributesItem
{
// Required:
Id fare_id;
double price = 0.0;
- CurrencyCode currency_code;
+ CurrencyCode currency_type;
FarePayment payment_method = FarePayment::BeforeBoarding;
FareTransfers transfers = FareTransfers::Unlimited;
@@ -794,7 +784,7 @@ struct Transfer
struct Pathway
{
// Required:
- Id pathway_d;
+ Id pathway_id;
Id from_stop_id;
Id to_stop_id;
PathwayMode pathway_mode = PathwayMode::Walkway;
@@ -853,7 +843,7 @@ struct FeedInfo
struct Translation
{
// Required:
- TranslationTable table_name = TranslationTable::Agency;
+ Text table_name;
Text field_name;
LanguageCode language;
Text translation;
@@ -895,6 +885,7 @@ using Calendar = std::vector<CalendarItem>;
using CalendarDates = std::vector<CalendarDate>;
using FareRules = std::vector<FareRule>;
+using FareAttributes = std::vector<FareAttributesItem>;
using Shapes = std::vector<ShapePoint>;
using Shape = std::vector<ShapePoint>;
using Frequencies = std::vector<Frequency>;
@@ -955,9 +946,14 @@ public:
inline Result read_fare_rules();
inline const FareRules & get_fare_rules() const;
- inline std::optional<FareRule> get_fare_rule(const Id & fare_id) const;
+ inline FareRules get_fare_rules(const Id & fare_id) const;
inline void add_fare_rule(const FareRule & fare_rule);
+ inline Result read_fare_attributes();
+ inline const FareAttributes & get_fare_attributes() const;
+ inline FareAttributes get_fare_attributes(const Id & fare_id) const;
+ inline void add_fare_attributes(const FareAttributesItem & fare_attributes_item);
+
inline Result read_shapes();
inline const Shapes & get_shapes() const;
inline Shape get_shape(const Id & shape_id, bool sort_by_sequence = true) const;
@@ -975,8 +971,8 @@ public:
inline Result read_pathways();
inline const Pathways & get_pathways() const;
- inline std::optional<Pathway> get_pathway(const Id & pathway_id) const;
- inline std::optional<Pathway> get_pathway(const Id & from_stop_id, const Id & to_stop_id) const;
+ inline Pathways get_pathways(const Id & pathway_id) const;
+ inline Pathways get_pathways(const Id & from_stop_id, const Id & to_stop_id) const;
inline void add_pathway(const Pathway & pathway);
inline Result read_levels();
@@ -990,7 +986,7 @@ public:
inline Result read_translations();
inline const Translations & get_translations() const;
- inline std::optional<Translation> get_translation(const TranslationTable & table_name) const;
+ inline Translations get_translations(const Text & table_name) const;
inline void add_translation(const Translation & translation);
inline Result read_attributions();
@@ -1001,16 +997,23 @@ private:
inline Result parse_csv(const std::string & filename,
const std::function<Result(const ParsedCsvRow & record)> & add_entity);
- inline Result add_agency(ParsedCsvRow const & row);
- inline Result add_route(ParsedCsvRow const & row);
- inline Result add_shape(ParsedCsvRow const & row);
- inline Result add_trip(ParsedCsvRow const & row);
- inline Result add_stop(ParsedCsvRow const & row);
- inline Result add_stop_time(ParsedCsvRow const & row);
- inline Result add_calendar_item(ParsedCsvRow const & row);
- inline Result add_calendar_date(ParsedCsvRow const & row);
- inline Result add_transfer(ParsedCsvRow const & row);
- inline Result add_frequency(ParsedCsvRow const & row);
+ inline Result add_agency(const ParsedCsvRow & row);
+ inline Result add_route(const ParsedCsvRow & row);
+ inline Result add_shape(const ParsedCsvRow & row);
+ inline Result add_trip(const ParsedCsvRow & row);
+ inline Result add_stop(const ParsedCsvRow & row);
+ inline Result add_stop_time(const ParsedCsvRow & row);
+ inline Result add_calendar_item(const ParsedCsvRow & row);
+ inline Result add_calendar_date(const ParsedCsvRow & row);
+ inline Result add_transfer(const ParsedCsvRow & row);
+ inline Result add_frequency(const ParsedCsvRow & row);
+ inline Result add_fare_attributes(const ParsedCsvRow & row);
+ inline Result add_fare_rule(const ParsedCsvRow & row);
+ inline Result add_pathway(const ParsedCsvRow & row);
+ inline Result add_level(const ParsedCsvRow & row);
+ inline Result add_feed_info(const ParsedCsvRow & row);
+ inline Result add_translation(const ParsedCsvRow & row);
+ inline Result add_attribution(const ParsedCsvRow & row);
std::string gtfs_directory;
@@ -1023,6 +1026,7 @@ private:
Calendar calendar;
CalendarDates calendar_dates;
FareRules fare_rules;
+ FareAttributes fare_attributes;
Shape shapes;
Frequencies frequencies;
Transfers transfers;
@@ -1033,62 +1037,72 @@ private:
FeedInfo feed_info;
};
-inline Feed::Feed(const std::string & gtfs_path) : gtfs_directory(gtfs_path) {
+inline Feed::Feed(const std::string & gtfs_path) : gtfs_directory(gtfs_path)
+{
if (!gtfs_directory.empty() && gtfs_directory.back() != '/')
gtfs_directory += "/";
}
+inline bool ErrorParsingOptionalFile(const Result & res)
+{
+ return res != ResultCode::OK && res != ResultCode::ERROR_FILE_ABSENT;
+}
+
inline Result Feed::read_feed()
{
// Read required files:
- if (auto res = read_agencies(); res.code != ResultCode::OK)
+ if (auto res = read_agencies(); res != ResultCode::OK)
return res;
- if (auto res = read_stops(); res.code != ResultCode::OK)
+ if (auto res = read_stops(); res != ResultCode::OK)
return res;
- if (auto res = read_routes(); res.code != ResultCode::OK)
+ if (auto res = read_routes(); res != ResultCode::OK)
return res;
- if (auto res = read_trips(); res.code != ResultCode::OK)
+ if (auto res = read_trips(); res != ResultCode::OK)
return res;
- if (auto res = read_stop_times(); res.code != ResultCode::OK)
+ if (auto res = read_stop_times(); res != ResultCode::OK)
return res;
// Read conditionally required files:
- if (auto res = read_calendar(); res.code != ResultCode::OK)
- {
- if (res != ResultCode::ERROR_FILE_ABSENT)
- return res;
- }
+ if (auto res = read_calendar(); ErrorParsingOptionalFile(res))
+ return res;
- if (auto res = read_calendar_dates(); res.code != ResultCode::OK)
- {
- if (res != ResultCode::ERROR_FILE_ABSENT)
- return res;
- }
+ if (auto res = read_calendar_dates(); ErrorParsingOptionalFile(res))
+ return res;
- // Optional files:
- if (auto res = read_shapes(); res.code != ResultCode::OK)
- {
- if (res != ResultCode::ERROR_FILE_ABSENT)
- return res;
- }
+ // Read optional files:
+ if (auto res = read_shapes(); ErrorParsingOptionalFile(res))
+ return res;
- if (auto res = read_transfers(); res.code != ResultCode::OK)
- {
- if (res != ResultCode::ERROR_FILE_ABSENT)
- return res;
- }
+ if (auto res = read_transfers(); ErrorParsingOptionalFile(res))
+ return res;
- if (auto res = read_frequencies(); res.code != ResultCode::OK)
- {
- if (res != ResultCode::ERROR_FILE_ABSENT)
- return res;
- }
+ if (auto res = read_frequencies(); ErrorParsingOptionalFile(res))
+ return res;
+
+ if (auto res = read_fare_attributes(); ErrorParsingOptionalFile(res))
+ return res;
+
+ if (auto res = read_fare_rules(); ErrorParsingOptionalFile(res))
+ return res;
+
+ if (auto res = read_pathways(); ErrorParsingOptionalFile(res))
+ return res;
- // TODO Read other conditionally optional and optional files
+ if (auto res = read_levels(); ErrorParsingOptionalFile(res))
+ return res;
+
+ if (auto res = read_attributions(); ErrorParsingOptionalFile(res))
+ return res;
+
+ if (auto res = read_feed_info(); ErrorParsingOptionalFile(res))
+ return res;
+
+ if (auto res = read_translations(); ErrorParsingOptionalFile(res))
+ return res;
return {ResultCode::OK, {}};
}
@@ -1101,7 +1115,7 @@ inline Result Feed::write_feed(const std::string & gtfs_path) const
return {};
}
-inline std::string get_value_or_default(ParsedCsvRow const & container, const std::string & key,
+inline std::string get_value_or_default(const ParsedCsvRow & container, const std::string & key,
const std::string & default_value = "")
{
const auto it = container.find(key);
@@ -1112,7 +1126,7 @@ inline std::string get_value_or_default(ParsedCsvRow const & container, const st
}
template <class T>
-inline void set_field(T & field, ParsedCsvRow const & container, const std::string & key,
+inline void set_field(T & field, const ParsedCsvRow & container, const std::string & key,
bool is_optional = true)
{
const std::string key_str = get_value_or_default(container, key);
@@ -1120,7 +1134,7 @@ inline void set_field(T & field, ParsedCsvRow const & container, const std::stri
field = static_cast<T>(std::stoi(key_str));
}
-inline bool set_fractional(double & field, ParsedCsvRow const & container, const std::string & key,
+inline bool set_fractional(double & field, const ParsedCsvRow & container, const std::string & key,
bool is_optional = true)
{
const std::string key_str = get_value_or_default(container, key);
@@ -1142,7 +1156,7 @@ inline void check_coordinates(double latitude, double longitude)
throw std::out_of_range("Longitude");
}
-inline Result Feed::add_agency(ParsedCsvRow const & row)
+inline Result Feed::add_agency(const ParsedCsvRow & row)
{
Agency agency;
@@ -1171,7 +1185,7 @@ inline Result Feed::add_agency(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_route(ParsedCsvRow const & row)
+inline Result Feed::add_route(const ParsedCsvRow & row)
{
Route route;
@@ -1215,7 +1229,7 @@ inline Result Feed::add_route(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_shape(ParsedCsvRow const & row)
+inline Result Feed::add_shape(const ParsedCsvRow & row)
{
ShapePoint point;
try
@@ -1246,7 +1260,7 @@ inline Result Feed::add_shape(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_trip(ParsedCsvRow const & row)
+inline Result Feed::add_trip(const ParsedCsvRow & row)
{
Trip trip;
try
@@ -1280,7 +1294,7 @@ inline Result Feed::add_trip(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_stop(ParsedCsvRow const & row)
+inline Result Feed::add_stop(const ParsedCsvRow & row)
{
Stop stop;
@@ -1324,7 +1338,7 @@ inline Result Feed::add_stop(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_stop_time(ParsedCsvRow const & row)
+inline Result Feed::add_stop_time(const ParsedCsvRow & row)
{
StopTime stop_time;
@@ -1369,7 +1383,7 @@ inline Result Feed::add_stop_time(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_calendar_item(ParsedCsvRow const & row)
+inline Result Feed::add_calendar_item(const ParsedCsvRow & row)
{
CalendarItem calendar_item;
try
@@ -1405,7 +1419,7 @@ inline Result Feed::add_calendar_item(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_calendar_date(ParsedCsvRow const & row)
+inline Result Feed::add_calendar_date(const ParsedCsvRow & row)
{
CalendarDate calendar_date;
try
@@ -1433,7 +1447,7 @@ inline Result Feed::add_calendar_date(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_transfer(ParsedCsvRow const & row)
+inline Result Feed::add_transfer(const ParsedCsvRow & row)
{
Transfer transfer;
try
@@ -1463,7 +1477,7 @@ inline Result Feed::add_transfer(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
-inline Result Feed::add_frequency(ParsedCsvRow const & row)
+inline Result Feed::add_frequency(const ParsedCsvRow & row)
{
Frequency frequency;
try
@@ -1494,6 +1508,263 @@ inline Result Feed::add_frequency(ParsedCsvRow const & row)
return {ResultCode::OK, {}};
}
+inline Result Feed::add_fare_attributes(const ParsedCsvRow & row)
+{
+ FareAttributesItem item;
+ try
+ {
+ // Required fields:
+ item.fare_id = row.at("fare_id");
+ set_fractional(item.price, row, "price", false);
+
+ item.currency_type = row.at("currency_type");
+ set_field(item.payment_method, row, "payment_method", false);
+ set_field(item.transfers, row, "transfers", false);
+
+ // Conditionally optional:
+ item.agency_id = get_value_or_default(row, "agency_id");
+ set_field(item.transfer_duration, row, "transfer_duration");
+ }
+ catch (const std::out_of_range & ex)
+ {
+ return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+ }
+ catch (const std::invalid_argument & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+ catch (const InvalidFieldFormat & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+
+ fare_attributes.emplace_back(item);
+ return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_fare_rule(const ParsedCsvRow & row)
+{
+ FareRule fare_rule;
+ try
+ {
+ // Required fields:
+ fare_rule.fare_id = row.at("fare_id");
+ }
+ catch (const std::out_of_range & ex)
+ {
+ return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+ }
+ catch (const std::invalid_argument & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+ catch (const InvalidFieldFormat & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+
+ // Optional fields:
+ fare_rule.route_id = get_value_or_default(row, "route_id");
+ fare_rule.origin_id = get_value_or_default(row, "origin_id");
+ fare_rule.destination_id = get_value_or_default(row, "destination_id");
+ fare_rule.contains_id = get_value_or_default(row, "contains_id");
+
+ fare_rules.emplace_back(fare_rule);
+
+ return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_pathway(const ParsedCsvRow & row)
+{
+ Pathway path;
+ try
+ {
+ // Required fields:
+ path.pathway_id = row.at("pathway_id");
+ path.from_stop_id = row.at("from_stop_id");
+ path.to_stop_id = row.at("to_stop_id");
+ set_field(path.pathway_mode, row, "pathway_mode", false);
+ set_field(path.is_bidirectional, row, "is_bidirectional", false);
+
+ // Optional fields:
+ set_fractional(path.length, row, "length");
+ set_field(path.traversal_time, row, "traversal_time");
+ set_field(path.stair_count, row, "stair_count");
+ set_fractional(path.max_slope, row, "max_slope");
+ set_fractional(path.min_width, row, "min_width");
+ }
+ catch (const std::out_of_range & ex)
+ {
+ return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+ }
+ catch (const std::invalid_argument & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+ catch (const InvalidFieldFormat & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+
+ path.signposted_as = get_value_or_default(row, "signposted_as");
+ path.reversed_signposted_as = get_value_or_default(row, "reversed_signposted_as");
+
+ pathways.emplace_back(path);
+ return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_level(const ParsedCsvRow & row)
+{
+ Level level;
+ try
+ {
+ // Required fields:
+ level.level_id = row.at("level_id");
+
+ set_fractional(level.level_index, row, "level_index", false);
+ }
+ catch (const std::out_of_range & ex)
+ {
+ return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+ }
+ catch (const std::invalid_argument & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+ catch (const InvalidFieldFormat & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+
+ // Optional field:
+ level.level_name = get_value_or_default(row, "level_name");
+
+ levels.emplace_back(level);
+
+ return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_feed_info(const ParsedCsvRow & row)
+{
+ try
+ {
+ // Required fields:
+ feed_info.feed_publisher_name = row.at("feed_publisher_name");
+ feed_info.feed_publisher_url = row.at("feed_publisher_url");
+ feed_info.feed_lang = row.at("feed_lang");
+
+ // Optional fields:
+ feed_info.feed_start_date = Date(get_value_or_default(row, "feed_start_date"));
+ feed_info.feed_end_date = Date(get_value_or_default(row, "feed_end_date"));
+ }
+ catch (const std::out_of_range & ex)
+ {
+ return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+ }
+ catch (const std::invalid_argument & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+ catch (const InvalidFieldFormat & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+
+ // Optional fields:
+ feed_info.feed_version = get_value_or_default(row, "feed_version");
+ feed_info.feed_contact_email = get_value_or_default(row, "feed_contact_email");
+ feed_info.feed_contact_url = get_value_or_default(row, "feed_contact_url");
+
+ return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_translation(const ParsedCsvRow & row)
+{
+ static std::vector<Text> available_tables{"agency", "stops", "routes", "trips",
+ "stop_times", "pathways", "levels"};
+
+ Translation translation;
+
+ try
+ {
+ // Required fields:
+ translation.table_name = row.at("table_name");
+ if (std::find(available_tables.begin(), available_tables.end(), translation.table_name) ==
+ available_tables.end())
+ {
+ throw InvalidFieldFormat("Field table_name of translations doesn't have required value");
+ }
+
+ translation.field_name = row.at("field_name");
+ translation.language = row.at("language");
+ translation.translation = row.at("translation");
+
+ // Conditionally required:
+ translation.record_id = get_value_or_default(row, "record_id");
+ translation.record_sub_id = get_value_or_default(row, "record_sub_id");
+ }
+ catch (const std::out_of_range & ex)
+ {
+ return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+ }
+ catch (const std::invalid_argument & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+ catch (const InvalidFieldFormat & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+
+ // Conditionally required:
+ translation.field_value = get_value_or_default(row, "field_value");
+
+ translations.emplace_back(translation);
+
+ return {ResultCode::OK, {}};
+}
+
+inline Result Feed::add_attribution(const ParsedCsvRow & row)
+{
+ Attribution attribution;
+
+ try
+ {
+ // Required fields:
+ attribution.organization_name = row.at("organization_name");
+
+ // Optional fields:
+ attribution.attribution_id = get_value_or_default(row, "attribution_id");
+ attribution.agency_id = get_value_or_default(row, "agency_id");
+ attribution.route_id = get_value_or_default(row, "route_id");
+ attribution.trip_id = get_value_or_default(row, "trip_id");
+
+ set_field(attribution.is_producer, row, "is_producer");
+ set_field(attribution.is_operator, row, "is_operator");
+ set_field(attribution.is_authority, row, "is_authority");
+
+ attribution.attribution_url = get_value_or_default(row, "attribution_url");
+ attribution.attribution_email = get_value_or_default(row, "attribution_email");
+ attribution.trip_id = get_value_or_default(row, "attribution_phone");
+ }
+ catch (const std::out_of_range & ex)
+ {
+ return {ResultCode::ERROR_REQUIRED_FIELD_ABSENT, ex.what()};
+ }
+ catch (const std::invalid_argument & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+ catch (const InvalidFieldFormat & ex)
+ {
+ return {ResultCode::ERROR_INVALID_FIELD_FORMAT, ex.what()};
+ }
+
+ attributions.emplace_back(attribution);
+
+ return {ResultCode::OK, {}};
+}
+
inline Result Feed::parse_csv(const std::string & filename,
const std::function<Result(const ParsedCsvRow & record)> & add_entity)
{
@@ -1712,26 +1983,51 @@ inline void Feed::add_calendar_date(const CalendarDate & calendar_date)
inline Result Feed::read_fare_rules()
{
- // TODO Read csv
- return {};
+ auto handler = [this](const ParsedCsvRow & record) { return this->add_fare_rule(record); };
+ return parse_csv("fare_rules.txt", handler);
}
inline const FareRules & Feed::get_fare_rules() const { return fare_rules; }
-inline std::optional<FareRule> Feed::get_fare_rule(const Id & fare_id) const
+inline FareRules Feed::get_fare_rules(const Id & fare_id) const
{
- const auto it =
- std::find_if(fare_rules.begin(), fare_rules.end(),
- [&fare_id](const FareRule & fare_rule) { return fare_rule.fare_id == fare_id; });
-
- if (it == fare_rules.end())
- return std::nullopt;
+ FareRules res;
+ for (const auto & fare_rule : fare_rules)
+ {
+ if (fare_rule.fare_id == fare_id)
+ res.emplace_back(fare_rule);
+ }
- return *it;
+ return res;
}
inline void Feed::add_fare_rule(const FareRule & fare_rule) { fare_rules.emplace_back(fare_rule); }
+inline Result Feed::read_fare_attributes()
+{
+ auto handler = [this](const ParsedCsvRow & record) { return this->add_fare_attributes(record); };
+ return parse_csv("fare_attributes.txt", handler);
+}
+
+inline const FareAttributes & Feed::get_fare_attributes() const { return fare_attributes; }
+
+FareAttributes Feed::get_fare_attributes(const Id & fare_id) const
+{
+ FareAttributes res;
+ for (const auto & attributes : fare_attributes)
+ {
+ if (attributes.fare_id == fare_id)
+ res.emplace_back(attributes);
+ }
+
+ return res;
+}
+
+inline void Feed::add_fare_attributes(const FareAttributesItem & fare_attributes_item)
+{
+ fare_attributes.emplace_back(fare_attributes_item);
+}
+
inline Result Feed::read_shapes()
{
auto handler = [this](const ParsedCsvRow & record) { return this->add_shape(record); };
@@ -1806,44 +2102,40 @@ inline void Feed::add_transfer(const Transfer & transfer) { transfers.emplace_ba
inline Result Feed::read_pathways()
{
- // TODO Read csv
- return {};
+ auto handler = [this](const ParsedCsvRow & record) { return this->add_pathway(record); };
+ return parse_csv("pathways.txt", handler);
}
inline const Pathways & Feed::get_pathways() const { return pathways; }
-inline std::optional<Pathway> Feed::get_pathway(const Id & pathway_id) const
+inline Pathways Feed::get_pathways(const Id & pathway_id) const
{
- const auto it = std::find_if(
- pathways.begin(), pathways.end(),
- [&pathway_id](const Pathway & pathway) { return pathway.pathway_d == pathway_id; });
-
- if (it == pathways.end())
- return std::nullopt;
-
- return *it;
+ Pathways res;
+ for (const auto & path : pathways)
+ {
+ if (path.pathway_id == pathway_id)
+ res.emplace_back(path);
+ }
+ return res;
}
-inline std::optional<Pathway> Feed::get_pathway(const Id & from_stop_id,
- const Id & to_stop_id) const
+inline Pathways Feed::get_pathways(const Id & from_stop_id, const Id & to_stop_id) const
{
- const auto it = std::find_if(
- pathways.begin(), pathways.end(), [&from_stop_id, &to_stop_id](const Pathway & pathway) {
- return pathway.from_stop_id == from_stop_id && pathway.to_stop_id == to_stop_id;
- });
-
- if (it == pathways.end())
- return std::nullopt;
-
- return *it;
+ Pathways res;
+ for (const auto & path : pathways)
+ {
+ if (path.from_stop_id == from_stop_id && path.to_stop_id == to_stop_id)
+ res.emplace_back(path);
+ }
+ return res;
}
inline void Feed::add_pathway(const Pathway & pathway) { pathways.emplace_back(pathway); }
inline Result Feed::read_levels()
{
- // TODO Read csv
- return {};
+ auto handler = [this](const ParsedCsvRow & record) { return this->add_level(record); };
+ return parse_csv("levels.txt", handler);
}
inline const Levels & Feed::get_levels() const { return levels; }
@@ -1864,8 +2156,8 @@ inline void Feed::add_level(const Level & level) { levels.emplace_back(level); }
inline Result Feed::read_feed_info()
{
- // TODO Read csv
- return {};
+ auto handler = [this](const ParsedCsvRow & record) { return this->add_feed_info(record); };
+ return parse_csv("feed_info.txt", handler);
}
inline FeedInfo Feed::get_feed_info() const { return feed_info; }
@@ -1874,23 +2166,21 @@ inline void Feed::set_feed_info(const FeedInfo & info) { feed_info = info; }
inline Result Feed::read_translations()
{
- // TODO Read csv
- return {};
+ auto handler = [this](const ParsedCsvRow & record) { return this->add_translation(record); };
+ return parse_csv("translations.txt", handler);
}
inline const Translations & Feed::get_translations() const { return translations; }
-inline std::optional<Translation> Feed::get_translation(const TranslationTable & table_name) const
+inline Translations Feed::get_translations(const Text & table_name) const
{
- const auto it = std::find_if(translations.begin(), translations.end(),
- [&table_name](const Translation & translation) {
- return translation.table_name == table_name;
- });
-
- if (it == translations.end())
- return std::nullopt;
-
- return *it;
+ Translations res;
+ for (const auto & translation : translations)
+ {
+ if (translation.table_name == table_name)
+ res.emplace_back(translation);
+ }
+ return res;
}
inline void Feed::add_translation(const Translation & translation)
@@ -1900,8 +2190,8 @@ inline void Feed::add_translation(const Translation & translation)
inline Result Feed::read_attributions()
{
- // TODO Read csv
- return {};
+ auto handler = [this](const ParsedCsvRow & record) { return this->add_attribution(record); };
+ return parse_csv("attributions.txt", handler);
}
inline const Attributions & Feed::get_attributions() const { return attributions; }
diff --git a/tests/data/sample_feed/attributions.txt b/tests/data/sample_feed/attributions.txt
new file mode 100644
index 0000000..1ce6af2
--- /dev/null
+++ b/tests/data/sample_feed/attributions.txt
@@ -0,0 +1,2 @@
+attribution_id,organization_name,is_producer,is_operator,is_authority,attribution_url
+0,Test inc,1,0,0,"https://test.pl/gtfs/" \ No newline at end of file
diff --git a/tests/data/sample_feed/feed_info.txt b/tests/data/sample_feed/feed_info.txt
new file mode 100644
index 0000000..7892e2d
--- /dev/null
+++ b/tests/data/sample_feed/feed_info.txt
@@ -0,0 +1,2 @@
+feed_publisher_name,feed_publisher_url,feed_lang,feed_version,feed_license
+"Test Solutions, Inc.",http://test,en,, \ No newline at end of file
diff --git a/tests/data/sample_feed/levels.txt b/tests/data/sample_feed/levels.txt
new file mode 100644
index 0000000..785b85c
--- /dev/null
+++ b/tests/data/sample_feed/levels.txt
@@ -0,0 +1,4 @@
+level_id,level_index,level_name
+U321L1,-1.5,"Vestibul"
+U321L2,-2,"Vestibul2"
+U321L0,0,"Povrch" \ No newline at end of file
diff --git a/tests/data/sample_feed/pathways.txt b/tests/data/sample_feed/pathways.txt
new file mode 100644
index 0000000..b5df5ff
--- /dev/null
+++ b/tests/data/sample_feed/pathways.txt
@@ -0,0 +1,4 @@
+pathway_id,from_stop_id,to_stop_id,pathway_mode,signposted_as,reversed_signposted_as,is_bidirectional
+T-A01C01,1073S,1098E,2,"Sign1","Sign2",1
+T-A01D01,1075S,1118S,1,"Sign4",,0
+T-A01D01,1075N,1118N,1,,,1 \ No newline at end of file
diff --git a/tests/data/sample_feed/transfers.txt b/tests/data/sample_feed/transfers.txt
new file mode 100644
index 0000000..c95a384
--- /dev/null
+++ b/tests/data/sample_feed/transfers.txt
@@ -0,0 +1,5 @@
+from_stop_id,to_stop_id,transfer_type,min_transfer_time
+130,4,2,70
+227,4,0,160
+314,11,1,
+385,11,2, \ No newline at end of file
diff --git a/tests/data/sample_feed/translations.txt b/tests/data/sample_feed/translations.txt
new file mode 100644
index 0000000..16d97ef
--- /dev/null
+++ b/tests/data/sample_feed/translations.txt
@@ -0,0 +1,2 @@
+table_name,field_name,language,translation,record_id,record_sub_id,field_value
+stop_times,stop_headsign,en,"Downtown",,, \ No newline at end of file
diff --git a/tests/unit_tests.cpp b/tests/unit_tests.cpp
index c3399f3..0271e9a 100644
--- a/tests/unit_tests.cpp
+++ b/tests/unit_tests.cpp
@@ -164,23 +164,39 @@ TEST_CASE("Empty container before parsing")
CHECK(!agency);
}
+TEST_CASE("Non existend directory")
+{
+ Feed feed("data/non_existing_dir");
+ REQUIRE_EQ(feed.read_transfers(), ResultCode::ERROR_FILE_ABSENT);
+ CHECK_EQ(feed.get_transfers().size(), 0);
+}
+
TEST_CASE("Transfers")
{
Feed feed("data/sample_feed");
- auto res = feed.read_transfers();
- REQUIRE_EQ(res.code, ResultCode::ERROR_FILE_ABSENT);
- CHECK_EQ(feed.get_transfers().size(), 0);
+ REQUIRE_EQ(feed.read_transfers(), ResultCode::OK);
+ const auto & transfers = feed.get_transfers();
+ CHECK_EQ(transfers.size(), 4);
+
+ CHECK_EQ(transfers[0].from_stop_id, "130");
+ CHECK_EQ(transfers[0].to_stop_id, "4");
+ CHECK_EQ(transfers[0].transfer_type, TransferType::MinimumTime);
+ CHECK_EQ(transfers[0].min_transfer_time, 70);
+
+ const auto & transfer = feed.get_transfer("314", "11");
+ REQUIRE(transfer);
+ CHECK_EQ(transfer.value().transfer_type, TransferType::Timed);
+ CHECK_EQ(transfer.value().min_transfer_time, 0);
}
TEST_CASE("Calendar")
{
Feed feed("data/sample_feed");
- auto res = feed.read_calendar();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_calendar(), ResultCode::OK);
const auto & calendar = feed.get_calendar();
REQUIRE_EQ(calendar.size(), 2);
- const auto calendar_record = feed.get_calendar("WE");
+ const auto & calendar_record = feed.get_calendar("WE");
REQUIRE(calendar_record);
CHECK_EQ(calendar_record->start_date, Date(2007, 01, 01));
@@ -198,12 +214,11 @@ TEST_CASE("Calendar")
TEST_CASE("Calendar dates")
{
Feed feed("data/sample_feed");
- auto res = feed.read_calendar_dates();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_calendar_dates(), ResultCode::OK);
const auto & calendar_dates = feed.get_calendar_dates();
REQUIRE_EQ(calendar_dates.size(), 1);
- const auto calendar_record = feed.get_calendar_dates("FULLW");
+ const auto & calendar_record = feed.get_calendar_dates("FULLW");
REQUIRE(!calendar_record.empty());
CHECK_EQ(calendar_record[0].date, Date(2007, 06, 04));
@@ -213,21 +228,32 @@ TEST_CASE("Calendar dates")
TEST_CASE("Read GTFS feed")
{
Feed feed("data/sample_feed");
- auto res = feed.read_feed();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_feed(), ResultCode::OK);
+
CHECK_EQ(feed.get_agencies().size(), 1);
CHECK_EQ(feed.get_routes().size(), 5);
CHECK_EQ(feed.get_trips().size(), 11);
CHECK_EQ(feed.get_shapes().size(), 8);
CHECK_EQ(feed.get_stops().size(), 9);
CHECK_EQ(feed.get_stop_times().size(), 28);
+ CHECK_EQ(feed.get_transfers().size(), 4);
+ CHECK_EQ(feed.get_frequencies().size(), 11);
+ CHECK_EQ(feed.get_attributions().size(), 1);
+ CHECK_EQ(feed.get_calendar().size(), 2);
+ CHECK_EQ(feed.get_calendar_dates().size(), 1);
+ CHECK_EQ(feed.get_fare_attributes().size(), 2);
+ CHECK_EQ(feed.get_fare_rules().size(), 4);
+ CHECK(!feed.get_feed_info().feed_publisher_name.empty());
+ CHECK_EQ(feed.get_levels().size(), 3);
+ CHECK_EQ(feed.get_pathways().size(), 3);
+ CHECK_EQ(feed.get_translations().size(), 1);
}
TEST_CASE("Agency")
{
Feed feed("data/sample_feed");
- auto res = feed.read_agencies();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_agencies(), ResultCode::OK);
+
const auto & agencies = feed.get_agencies();
REQUIRE_EQ(agencies.size(), 1);
CHECK_EQ(agencies[0].agency_id, "DTA");
@@ -243,8 +269,8 @@ TEST_CASE("Agency")
TEST_CASE("Routes")
{
Feed feed("data/sample_feed");
- auto res = feed.read_routes();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_routes(), ResultCode::OK);
+
const auto & routes = feed.get_routes();
REQUIRE_EQ(routes.size(), 5);
CHECK_EQ(routes[0].route_id, "AB");
@@ -256,15 +282,15 @@ TEST_CASE("Routes")
CHECK(routes[0].route_color.empty());
CHECK(routes[0].route_desc.empty());
- auto const route = feed.get_route("AB");
+ const auto & route = feed.get_route("AB");
CHECK(route);
}
TEST_CASE("Trips")
{
Feed feed("data/sample_feed");
- auto res = feed.read_trips();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_trips(), ResultCode::OK);
+
const auto & trips = feed.get_trips();
REQUIRE_EQ(trips.size(), 11);
@@ -276,7 +302,7 @@ TEST_CASE("Trips")
CHECK_EQ(trips[0].service_id, "FULLW");
CHECK_EQ(trips[0].trip_id, "AB1");
- auto const trip = feed.get_trip("AB1");
+ const auto & trip = feed.get_trip("AB1");
REQUIRE(trip);
CHECK(trip.value().trip_short_name.empty());
}
@@ -284,8 +310,7 @@ TEST_CASE("Trips")
TEST_CASE("Stops")
{
Feed feed("data/sample_feed");
- auto res = feed.read_stops();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_stops(), ResultCode::OK);
const auto & stops = feed.get_stops();
REQUIRE_EQ(stops.size(), 9);
@@ -299,15 +324,14 @@ TEST_CASE("Stops")
CHECK_EQ(stops[0].location_type, StopLocationType::GenericNode);
CHECK(stops[0].zone_id.empty());
- auto const stop = feed.get_stop("FUR_CREEK_RES");
- CHECK(stop);
+ auto const & stop = feed.get_stop("FUR_CREEK_RES");
+ REQUIRE(stop);
}
TEST_CASE("StopTimes")
{
Feed feed("data/sample_feed");
- auto res = feed.read_stop_times();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_stop_times(), ResultCode::OK);
const auto & stop_times = feed.get_stop_times();
REQUIRE_EQ(stop_times.size(), 28);
@@ -328,8 +352,7 @@ TEST_CASE("StopTimes")
TEST_CASE("Shapes")
{
Feed feed("data/sample_feed");
- auto res = feed.read_shapes();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_shapes(), ResultCode::OK);
const auto & shapes = feed.get_shapes();
REQUIRE_EQ(shapes.size(), 8);
@@ -339,15 +362,14 @@ TEST_CASE("Shapes")
CHECK_EQ(shapes[0].shape_pt_sequence, 50017);
CHECK_EQ(shapes[0].shape_dist_traveled, 12669);
- auto const shape = feed.get_shape("10237");
+ const auto & shape = feed.get_shape("10237");
CHECK_EQ(shape.size(), 4);
}
TEST_CASE("Calendar")
{
Feed feed("data/sample_feed");
- auto res = feed.read_calendar();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_calendar(), ResultCode::OK);
const auto & calendar = feed.get_calendar();
REQUIRE_EQ(calendar.size(), 2);
@@ -357,15 +379,14 @@ TEST_CASE("Calendar")
CHECK_EQ(calendar[0].monday, CalendarAvailability::Available);
CHECK_EQ(calendar[0].sunday, CalendarAvailability::Available);
- auto calendar_for_service = feed.get_calendar("FULLW");
+ const auto & calendar_for_service = feed.get_calendar("FULLW");
CHECK(calendar_for_service);
}
TEST_CASE("Calendar dates")
{
Feed feed("data/sample_feed");
- auto res = feed.read_calendar_dates();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_calendar_dates(), ResultCode::OK);
const auto & calendar_dates = feed.get_calendar_dates();
REQUIRE_EQ(calendar_dates.size(), 1);
@@ -373,15 +394,14 @@ TEST_CASE("Calendar dates")
CHECK_EQ(calendar_dates[0].date, Date(2007, 06, 04));
CHECK_EQ(calendar_dates[0].exception_type, CalendarDateException::Removed);
- auto calendar_dates_for_service = feed.get_calendar_dates("FULLW");
+ const auto & calendar_dates_for_service = feed.get_calendar_dates("FULLW");
CHECK_EQ(calendar_dates_for_service.size(), 1);
}
TEST_CASE("Frequencies")
{
Feed feed("data/sample_feed");
- auto res = feed.read_frequencies();
- REQUIRE_EQ(res.code, ResultCode::OK);
+ REQUIRE_EQ(feed.read_frequencies(), ResultCode::OK);
const auto & frequencies = feed.get_frequencies();
REQUIRE_EQ(frequencies.size(), 11);
@@ -389,7 +409,128 @@ TEST_CASE("Frequencies")
CHECK_EQ(frequencies[0].start_time, Time(6, 00, 00));
CHECK_EQ(frequencies[0].end_time, Time(22, 00, 00));
CHECK_EQ(frequencies[0].headway_secs, 1800);
- auto const frequencies_for_trip = feed.get_frequencies("CITY1");
+
+ const auto & frequencies_for_trip = feed.get_frequencies("CITY1");
CHECK_EQ(frequencies_for_trip.size(), 5);
}
+
+TEST_CASE("Fare attributes")
+{
+ Feed feed("data/sample_feed");
+ REQUIRE_EQ(feed.read_fare_attributes(), ResultCode::OK);
+
+ const auto & attributes = feed.get_fare_attributes();
+ REQUIRE_EQ(attributes.size(), 2);
+ CHECK_EQ(attributes[0].fare_id, "p");
+ CHECK_EQ(attributes[0].price, 1.25);
+ CHECK_EQ(attributes[0].currency_type, "USD");
+ CHECK_EQ(attributes[0].payment_method, FarePayment::OnBoard);
+ CHECK_EQ(attributes[0].transfers, FareTransfers::No);
+ CHECK_EQ(attributes[0].transfer_duration, 0);
+
+ const auto & attributes_for_id = feed.get_fare_attributes("a");
+ REQUIRE_EQ(attributes_for_id.size(), 1);
+ CHECK_EQ(attributes_for_id[0].price, 5.25);
+}
+
+TEST_CASE("Fare rules")
+{
+ Feed feed("data/sample_feed");
+ REQUIRE_EQ(feed.read_fare_rules(), ResultCode::OK);
+
+ const auto & fare_rules = feed.get_fare_rules();
+ REQUIRE_EQ(fare_rules.size(), 4);
+ CHECK_EQ(fare_rules[0].fare_id, "p");
+ CHECK_EQ(fare_rules[0].route_id, "AB");
+
+ const auto & rules_for_id = feed.get_fare_rules("p");
+ REQUIRE_EQ(rules_for_id.size(), 3);
+ CHECK_EQ(rules_for_id[1].route_id, "STBA");
+}
+
+TEST_CASE("Levels")
+{
+ Feed feed("data/sample_feed");
+ REQUIRE_EQ(feed.read_levels(), ResultCode::OK);
+
+ const auto & levels = feed.get_levels();
+ REQUIRE_EQ(levels.size(), 3);
+ CHECK_EQ(levels[0].level_id, "U321L1");
+ CHECK_EQ(levels[0].level_index, -1.5);
+
+ const auto & level = feed.get_level("U321L2");
+ REQUIRE(level);
+
+ CHECK_EQ(level.value().level_index, -2);
+ CHECK_EQ(level.value().level_name, "Vestibul2");
+}
+
+TEST_CASE("Pathways")
+{
+ Feed feed("data/sample_feed");
+ REQUIRE_EQ(feed.read_pathways(), ResultCode::OK);
+
+ const auto & pathways = feed.get_pathways();
+ REQUIRE_EQ(pathways.size(), 3);
+ CHECK_EQ(pathways[0].pathway_id, "T-A01C01");
+ CHECK_EQ(pathways[0].from_stop_id, "1073S");
+ CHECK_EQ(pathways[0].to_stop_id, "1098E");
+ CHECK_EQ(pathways[0].pathway_mode, PathwayMode::Stairs);
+ CHECK_EQ(pathways[0].signposted_as, "Sign1");
+ CHECK_EQ(pathways[0].reversed_signposted_as, "Sign2");
+ CHECK_EQ(pathways[0].is_bidirectional, PathwayDirection::Bidirectional);
+
+ const auto & pathways_by_id = feed.get_pathways("T-A01D01");
+ REQUIRE_EQ(pathways_by_id.size(), 2);
+ CHECK_EQ(pathways_by_id[0].is_bidirectional, PathwayDirection::Unidirectional);
+ CHECK(pathways_by_id[0].reversed_signposted_as.empty());
+}
+
+TEST_CASE("Translations")
+{
+ Feed feed("data/sample_feed");
+ REQUIRE_EQ(feed.read_translations(), ResultCode::OK);
+
+ const auto & translations = feed.get_translations();
+ REQUIRE_EQ(translations.size(), 1);
+ CHECK_EQ(translations[0].table_name, "stop_times");
+ CHECK_EQ(translations[0].field_name, "stop_headsign");
+ CHECK_EQ(translations[0].language, "en");
+ CHECK_EQ(translations[0].translation, "Downtown");
+ CHECK(translations[0].record_id.empty());
+ CHECK(translations[0].record_sub_id.empty());
+ CHECK(translations[0].field_value.empty());
+
+ CHECK_EQ(feed.get_translations("stop_times").size(), 1);
+}
+
+TEST_CASE("Attributions")
+{
+ Feed feed("data/sample_feed");
+ REQUIRE_EQ(feed.read_attributions(), ResultCode::OK);
+
+ const auto & attributions = feed.get_attributions();
+ REQUIRE_EQ(attributions.size(), 1);
+ CHECK_EQ(attributions[0].attribution_id, "0");
+ CHECK_EQ(attributions[0].organization_name, "Test inc");
+ CHECK_EQ(attributions[0].is_producer, AttributionRole::Yes);
+ CHECK_EQ(attributions[0].is_operator, AttributionRole::No);
+ CHECK_EQ(attributions[0].is_authority, AttributionRole::No);
+ CHECK_EQ(attributions[0].attribution_url, "https://test.pl/gtfs/");
+ CHECK(attributions[0].attribution_email.empty());
+ CHECK(attributions[0].attribution_phone.empty());
+}
+
+TEST_CASE("Feed info")
+{
+ Feed feed("data/sample_feed");
+ REQUIRE_EQ(feed.read_feed_info(), ResultCode::OK);
+
+ const auto & info = feed.get_feed_info();
+
+ CHECK_EQ(info.feed_publisher_name, "Test Solutions, Inc.");
+ CHECK_EQ(info.feed_publisher_url, "http://test");
+ CHECK_EQ(info.feed_lang, "en");
+}
+
TEST_SUITE_END();