diff --git a/.gitignore b/.gitignore index bfe0a6c..746ab9c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ .rspec_status .rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-master-ci-rubocop-yml Gemfile.lock +rubocop-87c7cdd254a8d09d005ee06efac7acc0.yml +.claude/ diff --git a/.rubocop.yml b/.rubocop.yml index c8927fd..b21db72 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -7,6 +7,6 @@ require: rubocop-rails inherit_from: - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml AllCops: - TargetRubyVersion: 2.7 + TargetRubyVersion: 3.2 Rails: Enabled: false diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9a0b901 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,59 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What is relaton-xsf? + +A Ruby gem for bibliographic retrieval of XMPP XEP (XMPP Extension Protocol) specifications. Part of the Relaton family of gems. Fetches data from https://xmpp.org/extensions/refs/ and the relaton-data-xsf GitHub repository. + +## Commands + +- `bundle exec rspec` — run all tests +- `bundle exec rspec spec/relaton/xsf/processor_spec.rb` — run a single spec file +- `bundle exec rubocop` — lint +- `bundle exec rubocop -a` — lint with auto-fix +- `bin/console` — interactive console with gem loaded + +## Architecture + +Namespace: `Relaton::Xsf` (under `lib/relaton/xsf/`). Branch `lutaml-integration` uses the new nested namespace (not the old `RelatonXsf`). + +Key classes and their base classes from relaton-core: + +| Class | Base | Role | +|---|---|---| +| `Processor` | `Relaton::Core::Processor` | Plugin entry point for relaton registry | +| `Bibliography` | Module (extends self) | Search & get interface (`search`, `get`) | +| `HitCollection` | `Relaton::Core::HitCollection` | Collection of search results | +| `Hit` | `Relaton::Core::Hit` | Single result; lazy-loads YAML from GitHub | +| `DataFetcher` | `Relaton::Core::DataFetcher` | Crawls xmpp.org, parses BibXML, saves docs | +| `Item` / `Bibitem` / `Bibdata` | `Relaton::Bib::Item` | Bibliographic item models (lutaml-model based) | + +Data flow: `Processor#get` → `Bibliography.get` → `HitCollection.search` → `Hit#item` → fetches YAML → `Relaton::Bib::Item.from_yaml` + +DataFetcher flow: Crawls `https://xmpp.org/extensions/refs/`, parses each XML ref via `Relaton::Bib::Converter::BibXml.to_item`, sets `ext.flavor = "xsf"`, saves to disk. + +Constants: `INDEXFILE = "index-v1"`, `GHDATA_URL` points to relaton-data-xsf `data-v2` branch. + +## Testing + +- **Index fixture:** `spec/fixtures/index-v1.zip` is pre-loaded into `Relaton::Index` pool in `before(:suite)` (configured in `spec/support/webmock.rb`). Run `rake spec:update_index` to refresh from relaton-data-xsf. +- RSpec with VCR cassettes (`spec/vcr_cassettes/`) for HTTP interactions +- WebMock disables all external network connections +- Fixtures in `spec/fixtures/` (item.yaml, bibdata.xml, bibitem.xml) +- Round-trip tests verify YAML→Item→YAML and XML→Item→XML fidelity +- `DataFetcher` is lazily required — specs that test it must `require "relaton/xsf/data_fetcher"` explicitly +- Same for `Processor` — `require "relaton/xsf/processor"` + +## Key dependencies + +- `relaton-core` — abstract base classes (Processor, HitCollection, Hit, DataFetcher) +- `relaton-bib` — bibliographic models, XML/YAML serialization (lutaml-model based) +- `relaton-index` — index management for quick document lookups +- `mechanize` — HTTP fetching and HTML parsing + +## Style + +- RuboCop with relaton shared config (inherits from riboseinc/oss-guides) +- Target Ruby version: 3.1 +- Logging via `Relaton::Xsf::Util` (extends `Relaton::Bib::Util`, PROGNAME = "relaton-xsf") diff --git a/README.adoc b/README.adoc index 68999d1..2f3c6cf 100644 --- a/README.adoc +++ b/README.adoc @@ -1,4 +1,4 @@ -= Relaton-XSF: bibliographic retrieval of XMPP XEP specifications += Relaton::XSF: bibliographic retrieval of XMPP XEP specifications image:https://img.shields.io/gem/v/relaton-xsf.svg["Gem Version", link="https://rubygems.org/gems/relaton-xsf"] image:https://github.com/relaton/relaton-xsf/workflows/macos/badge.svg["Build Status (macOS)", link="https://github.com/relaton/relaton-xsf/actions?workflow=macos"] @@ -42,14 +42,14 @@ Or install it yourself as: [source,ruby] ---- -require 'relaton_xsf' +require 'relaton/xsf' => true -hit_collection = RelatonXsf::Bibliography.search("XEP 0001") -=> +hit_collection = Relaton::Xsf::Bibliography.search("XEP 0001") +=> -item = hit_collection[0].fetch -=> # # " - 2023-07-18 - XMPP Extension Protocols +=> " + 2026-03-04 + XMPP Extension Protocols + http://xmpp.org/extensions/xep-0001.html + http://xmpp.org/extensions/xep-0001.html + XEP 0001 ... " ---- @@ -67,12 +70,16 @@ With argument `bibdata: true` it outputs XML wrapped by `bibdata` element and ad [source,ruby] ---- item.to_xml bibdata: true -=> " - 2023-07-18 - XMPP Extension Protocols +=> " + 2026-03-04 + XMPP Extension Protocols + http://xmpp.org/extensions/xep-0001.html + http://xmpp.org/extensions/xep-0001.html + XEP 0001 ... rfc + xsf " ---- @@ -80,35 +87,34 @@ item.to_xml bibdata: true === Get document by reference [source,ruby] ---- -item = RelatonXsf::Bibliography.get "XEP 0001" -[relaton-xsf] (XEP 0001) Fetching from Relaton repository ... -[relaton-xsf] (XEP 0001) Found `XEP 0001` -=> # # "XEP 0001" ---- -=== Typed links +=== Typed source links -XSF publications have `src` type link. +XSF publications have `src` type source link. [source,ruby] ---- -item.link -=> [#, - @language=nil, - @script=nil, - @type="src">] +item.source[0].type +=> "src" + +item.source[0].content +=> "http://xmpp.org/extensions/xep-0001.html" ---- === Fetch data This gem uses the https://xmpp.org/extensions/refs/ dataset as a data source. -The method `RelatonXsf::DataFetcher.fetch(output: "data", format: "yaml")` fetches all the documents from the dataset and saves them to the `./data` folder in YAML format. +The method `Relaton::Xsf::DataFetcher.fetch(output: "data", format: "yaml")` fetches all the documents from the dataset and saves them to the `./data` folder in YAML format. Arguments: - `output` - folder to save documents (default './data'). @@ -116,7 +122,9 @@ Arguments: [source,ruby] ---- -RelatonXsf::DataFetcher.fetch +require 'relaton/xsf/data_fetcher' + +Relaton::Xsf::DataFetcher.fetch Started at: 2021-09-01 18:01:01 +0200 Stopped at: 2021-09-01 18:01:43 +0200 Done in: 42 sec. @@ -125,12 +133,14 @@ Done in: 42 sec. === Logging -RelatonXsf uses the relaton-logger gem for logging. By default, it logs to STDOUT. To change the log levels and add other loggers, read the https://github.com/relaton/relaton-logger#usage[relaton-logger] documentation. +Relaton::Xsf uses the relaton-logger gem for logging. By default, it logs to STDOUT. To change the log levels and add other loggers, read the https://github.com/relaton/relaton-logger#usage[relaton-logger] documentation. == Development After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. +To update the index test fixture (used by tests), run `rake spec:update_index`. This downloads the latest `index-v1.zip` from the https://github.com/relaton/relaton-data-xsf[relaton-data-xsf] repository. + To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to https://rubygems.org[rubygems.org]. == Contributing diff --git a/Rakefile b/Rakefile index 156fd6e..cc50b03 100644 --- a/Rakefile +++ b/Rakefile @@ -10,3 +10,25 @@ require "rubocop/rake_task" RuboCop::RakeTask.new task default: %i[spec] + +namespace :spec do + desc "Download latest XSF index fixture from relaton-data-xsf" + task :update_index do + require "net/http" + require "uri" + + url = "https://raw.githubusercontent.com/relaton/relaton-data-xsf/data-v2/index-v1.zip" + dest = File.join(__dir__, "spec", "fixtures", "index-v1.zip") + + puts "Downloading \#{url} ..." + uri = URI.parse(url) + response = Net::HTTP.get_response(uri) + + if response.is_a?(Net::HTTPSuccess) + File.binwrite(dest, response.body) + puts "Updated \#{dest} (\#{response.body.bytesize} bytes)" + else + abort "Failed to download: HTTP \#{response.code}" + end + end +end diff --git a/bin/console b/bin/console index a69bca0..9db49fa 100755 --- a/bin/console +++ b/bin/console @@ -2,7 +2,7 @@ # frozen_string_literal: true require "bundler/setup" -require "relaton_xsf" +require "relaton/xsf" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. diff --git a/lib/relaton/xsf.rb b/lib/relaton/xsf.rb new file mode 100644 index 0000000..a897105 --- /dev/null +++ b/lib/relaton/xsf.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require "mechanize" +require "relaton/index" +require "relaton/bib" +require_relative "xsf/version" +require_relative "xsf/util" +require_relative "xsf/item" +require_relative "xsf/bibitem" +require_relative "xsf/bibdata" +require_relative "xsf/hit" +require_relative "xsf/hit_collection" +require_relative "xsf/bibliography" + +module Relaton + module Xsf + INDEXFILE = "index-v1" + + class Error < StandardError; end + + # Your code goes here... + def self.grammar_hash + # gem_path = File.expand_path "..", __dir__ + # grammars_path = File.join gem_path, "grammars", "*" + # grammars = Dir[grammars_path].sort.map { |gp| File.read gp }.join + Digest::MD5.hexdigest Relaton::Bib::VERSION # grammars + end + end +end diff --git a/lib/relaton/xsf/bibdata.rb b/lib/relaton/xsf/bibdata.rb new file mode 100644 index 0000000..1fe6025 --- /dev/null +++ b/lib/relaton/xsf/bibdata.rb @@ -0,0 +1,7 @@ +module Relaton + module Xsf + class Bibdata < Item + include Bib::BibdataShared + end + end +end diff --git a/lib/relaton/xsf/bibitem.rb b/lib/relaton/xsf/bibitem.rb new file mode 100644 index 0000000..e804aeb --- /dev/null +++ b/lib/relaton/xsf/bibitem.rb @@ -0,0 +1,7 @@ +module Relaton + module Xsf + class Bibitem < Item + include Bib::BibitemShared + end + end +end diff --git a/lib/relaton/xsf/bibliography.rb b/lib/relaton/xsf/bibliography.rb new file mode 100644 index 0000000..c6a7245 --- /dev/null +++ b/lib/relaton/xsf/bibliography.rb @@ -0,0 +1,24 @@ +module Relaton + module Xsf + module Bibliography + extend self + + def search(ref) + HitCollection.new(ref).search + end + + def get(code, _year = nil, _opts = {}) + Util.info "Fetching from Relaton repository ...", key: code + result = search(code) + if result.empty? + Util.info "Not found.", key: code + return + end + + bib = result.first.item + Util.info "Found: `#{bib.docidentifier.first.content}`", key: code + bib + end + end + end +end diff --git a/lib/relaton/xsf/data_fetcher.rb b/lib/relaton/xsf/data_fetcher.rb new file mode 100644 index 0000000..bade99c --- /dev/null +++ b/lib/relaton/xsf/data_fetcher.rb @@ -0,0 +1,56 @@ +require "relaton/core" + +module Relaton + module Xsf + class DataFetcher < Relaton::Core::DataFetcher + def index + @index ||= Relaton::Index.find_or_create :xsf, file: "#{INDEXFILE}.yaml" + end + + def fetch(_source = nil) + agent = Mechanize.new + resp = agent.get "https://xmpp.org/extensions/refs/" + resp.xpath("//a[contains(@href, 'XEP-')]").each do |link| + doc = agent.get link[:href] + bib = Relaton::Bib::Converter::BibXml.to_item doc.body + save_doc bib + rescue StandardError => e + Util.warn "Failed to parse #{link[:href]}: #{e.message}" + end + index.save + end + + def save_doc(bib) + return unless bib + + bib.ext ||= Relaton::Bib::Ext.new + bib.ext.flavor = "xsf" + + docid = bib.docidentifier.detect(&:primary) || bib.docidentifier.first + id = docid&.content + return unless id + + file = output_file id + if @files.include? file + Util.warn "File #{file} already exists" + else + @files << file + end + File.write file, serialize(bib), encoding: "UTF-8" + index.add_or_update id, file + end + + def to_yaml(bib) + bib.to_yaml + end + + def to_xml(bib) + bib.to_xml bibdata: true + end + + def to_bibxml(bib) + bib.to_rfcxml + end + end + end +end diff --git a/lib/relaton/xsf/hit.rb b/lib/relaton/xsf/hit.rb new file mode 100644 index 0000000..158e957 --- /dev/null +++ b/lib/relaton/xsf/hit.rb @@ -0,0 +1,17 @@ +module Relaton + module Xsf + class Hit < Relaton::Core::Hit + def item + return @doc if @doc + + agent = Mechanize.new + resp = agent.get hit[:url] + hash = YAML.safe_load resp.body + hash["fetched"] = Date.today.to_s + @doc = Relaton::Bib::Item.from_yaml hash.to_yaml + rescue StandardError => e + raise Relaton::RequestError, e.message + end + end + end +end diff --git a/lib/relaton/xsf/hit_collection.rb b/lib/relaton/xsf/hit_collection.rb new file mode 100644 index 0000000..8673555 --- /dev/null +++ b/lib/relaton/xsf/hit_collection.rb @@ -0,0 +1,24 @@ +module Relaton + module Xsf + class HitCollection < Relaton::Core::HitCollection + GHDATA_URL = "https://raw.githubusercontent.com/relaton/relaton-data-xsf/data-v2/".freeze + + def search + @array = index.search(ref).sort_by { |hit| hit[:id] }.map do |row| + Hit.new url: "#{GHDATA_URL}#{row[:file]}" + end + self + rescue StandardError => e + raise Relaton::RequestError, e.message + end + + def index + @index ||= Relaton::Index.find_or_create( + :xsf, + url: "#{GHDATA_URL}#{INDEXFILE}.zip", + file: "#{INDEXFILE}.yaml", + ) + end + end + end +end diff --git a/lib/relaton/xsf/item.rb b/lib/relaton/xsf/item.rb new file mode 100644 index 0000000..e411ad3 --- /dev/null +++ b/lib/relaton/xsf/item.rb @@ -0,0 +1,7 @@ +module Relaton + module Xsf + class Item < Bib::Item + model Bib::ItemData + end + end +end diff --git a/lib/relaton/xsf/processor.rb b/lib/relaton/xsf/processor.rb new file mode 100644 index 0000000..54df331 --- /dev/null +++ b/lib/relaton/xsf/processor.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true +require "relaton/core/processor" + +module Relaton + module Xsf + class Processor < Relaton::Core::Processor + attr_reader :idtype + + def initialize # rubocop:disable Lint/MissingSuper + @short = :relaton_xsf + @prefix = "XEP" + @defaultprefix = %r{^XEP\s} + @idtype = "XEP" + @datasets = %w[xep-xmpp] + end + + def get(code, date, opts) + require_relative "../xsf" + Relaton::Xsf::Bibliography.get(code, date, opts) + end + + def fetch_data(_source, opts) + require_relative "data_fetcher" + Relaton::Xsf::DataFetcher.fetch(**opts) + end + + def from_xml(xml) + require_relative "../xsf" + Relaton::Bib::Item.from_xml(xml) + end + + def from_yaml(yaml) + require_relative "../xsf" + Relaton::Bib::Item.from_yaml(yaml) + end + + def grammar_hash + require_relative "../xsf" + Relaton::Xsf.grammar_hash + end + + def remove_index_file + require_relative "../xsf" + Relaton::Index.find_or_create( + :xsf, url: true, file: "#{INDEXFILE}.yaml" + ).remove_file + end + end + end +end diff --git a/lib/relaton/xsf/util.rb b/lib/relaton/xsf/util.rb new file mode 100644 index 0000000..5e76714 --- /dev/null +++ b/lib/relaton/xsf/util.rb @@ -0,0 +1,9 @@ +module Relaton + module Xsf + module Util + extend Relaton::Bib::Util + + PROGNAME = "relaton-xsf".freeze + end + end +end diff --git a/lib/relaton/xsf/version.rb b/lib/relaton/xsf/version.rb new file mode 100644 index 0000000..137da84 --- /dev/null +++ b/lib/relaton/xsf/version.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Relaton + module Xsf + VERSION = "2.0.0" + end +end diff --git a/lib/relaton_xsf.rb b/lib/relaton_xsf.rb deleted file mode 100644 index 2f15c88..0000000 --- a/lib/relaton_xsf.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true - -require "mechanize" -require "relaton/index" -require "relaton_bib" -require_relative "relaton_xsf/version" -require_relative "relaton_xsf/util" -require_relative "relaton_xsf/bibliographic_item" -require_relative "relaton_xsf/bibliography" -require_relative "relaton_xsf/hit_collection" -require_relative "relaton_xsf/hit" -require_relative "relaton_xsf/xml_parser" -require_relative "relaton_xsf/bibxml_parser" -require_relative "relaton_xsf/hash_converter" -require_relative "relaton_xsf/data_fetcher" - -module RelatonXsf - class Error < StandardError; end - # Your code goes here... -end diff --git a/lib/relaton_xsf/bibliographic_item.rb b/lib/relaton_xsf/bibliographic_item.rb deleted file mode 100644 index 178ff9e..0000000 --- a/lib/relaton_xsf/bibliographic_item.rb +++ /dev/null @@ -1,4 +0,0 @@ -module RelatonXsf - class BibliographicItem < RelatonBib::BibliographicItem - end -end diff --git a/lib/relaton_xsf/bibliography.rb b/lib/relaton_xsf/bibliography.rb deleted file mode 100644 index be7507e..0000000 --- a/lib/relaton_xsf/bibliography.rb +++ /dev/null @@ -1,22 +0,0 @@ -module RelatonXsf - module Bibliography - extend self - - def search(ref) - HitCollection.new(ref).search - end - - def get(code, _year = nil, _opts = {}) - Util.info "Fetching from Relaton repository ...", key: code - result = search(code) - if result.empty? - Util.info "Not found.", key: code - return - end - - bib = result.first.fetch - Util.info "Found: `#{bib.docidentifier.first.id}`", key: code - bib - end - end -end diff --git a/lib/relaton_xsf/bibxml_parser.rb b/lib/relaton_xsf/bibxml_parser.rb deleted file mode 100644 index 45aa60b..0000000 --- a/lib/relaton_xsf/bibxml_parser.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RelatonXsf - module BibXMLParser - include RelatonBib::BibXMLParser - extend self - - # @param item_hash [Hash] - # @return [RelatonXsf::BibliographicItem] - def bib_item(item_hash) - BibliographicItem.new(**item_hash) - end - end -end diff --git a/lib/relaton_xsf/data_fetcher.rb b/lib/relaton_xsf/data_fetcher.rb deleted file mode 100644 index 4c3dfc5..0000000 --- a/lib/relaton_xsf/data_fetcher.rb +++ /dev/null @@ -1,57 +0,0 @@ -module RelatonXsf - class DataFetcher - # @param output [String] - # @param format [String] - def initialize(output, format) - @output = output - @format = format - @ext = format.sub(/^bib/, "") - @files = [] - end - - def self.fetch(output: "data", format: "yaml") - warn "fetching data to #{output} in #{format} format" - t1 = Time.now - warn "start at #{t1}" - FileUtils.mkdir_p output - new(output, format).fetch - t2 = Time.now - t = t2 - t1 - warn "finished at #{t2} (#{t.round} seconds)" - end - - def index - @index ||= Relaton::Index.find_or_create :xsf, file: "index-v1.yaml" - end - - def fetch - agent = Mechanize.new - resp = agent.get "https://xmpp.org/extensions/refs/" - resp.xpath("//a[contains(@href, 'XEP-')]").each do |link| - doc = agent.get link[:href] - bib = BibXMLParser.parse doc.body - write_doc bib - end - index.save - end - - def write_doc(bib) - id = bib.docidentifier.find(&:primary).id - file = File.join @output, "#{id.gsub(' ', '-').downcase}.#{@ext}" - if @files.include? file - Util.warn "#{file} already exists" - end - File.write file, serialize(bib), encoding: "UTF-8" - @files << file - index.add_or_update id, file - end - - def serialize(bib) - case @format - when "yaml" then bib.to_hash.to_yaml - when "xml" then bib.to_xml bibdata: true - else bib.send "to_#{@format}" - end - end - end -end diff --git a/lib/relaton_xsf/hash_converter.rb b/lib/relaton_xsf/hash_converter.rb deleted file mode 100644 index 5dfb0d0..0000000 --- a/lib/relaton_xsf/hash_converter.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RelatonXsf - module HashConverter - include RelatonBib::HashConverter - extend self - # @param item_hash [Hash] - # @return [RelatonBib::BibliographicItem] - def bib_item(item_hash) - BibliographicItem.new(**item_hash) - end - end -end diff --git a/lib/relaton_xsf/hit.rb b/lib/relaton_xsf/hit.rb deleted file mode 100644 index 54a28a7..0000000 --- a/lib/relaton_xsf/hit.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RelatonXsf - class Hit < RelatonBib::Hit - def fetch - return @doc if @doc - - agent = Mechanize.new - resp = agent.get hit[:url] - hash = YAML.safe_load resp.body - hash["fetched"] = Date.today.to_s - @doc = BibliographicItem.from_hash hash - rescue StandardError => e - raise RelatonBib::RequestError, e.message - end - end -end diff --git a/lib/relaton_xsf/hit_collection.rb b/lib/relaton_xsf/hit_collection.rb deleted file mode 100644 index 9badadd..0000000 --- a/lib/relaton_xsf/hit_collection.rb +++ /dev/null @@ -1,19 +0,0 @@ -module RelatonXsf - class HitCollection < RelatonBib::HitCollection - INDEX_FILE = "index-v1.yaml".freeze - GHDATA_URL = "https://raw.githubusercontent.com/relaton/relaton-data-xsf/main/".freeze - - def search - @array = index.search(text).sort_by { |hit| hit[:id] }.map do |row| - Hit.new url: "#{GHDATA_URL}#{row[:file]}" - end - self - rescue StandardError => e - raise RelatonBib::RequestError, e.message - end - - def index - @index ||= Relaton::Index.find_or_create :xsf, url: "#{GHDATA_URL}index-v1.zip", file: INDEX_FILE - end - end -end diff --git a/lib/relaton_xsf/processor.rb b/lib/relaton_xsf/processor.rb deleted file mode 100644 index 17d9fe2..0000000 --- a/lib/relaton_xsf/processor.rb +++ /dev/null @@ -1,61 +0,0 @@ -require "relaton/processor" - -module RelatonXsf - class Processor < Relaton::Processor - attr_reader :idtype - - def initialize # rubocop:disable Lint/MissingSuper - @short = :relaton_xsf - @prefix = "XEP" - @defaultprefix = %r{^XEP\s} - @idtype = "XEP" - @datasets = %w[xep-xmpp] - end - - # @param code [String] - # @param date [String, nil] year - # @param opts [Hash] - # @return [RelatonXsf::BibliographicItem] - def get(code, date, opts) - ::RelatonXsf::Bibliography.get(code, date, opts) - end - - # - # Fetch all the documents from http://xml2rfc.tools.ietf.org/public/rfc/bibxml-3gpp-new/ - # - # @param [String] source source name - # @param [Hash] opts - # @option opts [String] :output directory to output documents - # @option opts [String] :format - # - def fetch_data(_source, opts) - ::RelatonXsf::DataFetcher.fetch(**opts) - end - - # @param xml [String] - # @return [RelatonXsf::BibliographicItem] - def from_xml(xml) - ::RelatonXsf::XMLParser.from_xml xml - end - - # @param hash [Hash] - # @return [RelatonXsf::BibliographicItem] - def hash_to_bib(hash) - item_hash = ::RelatonXsf::HashConverter.hash_to_bib(hash) - ::RelatonXsf::BibliographicItem.new(**item_hash) - end - - # Returns hash of XML grammar - # @return [String] - def grammar_hash - @grammar_hash ||= ::RelatonBib.grammar_hash - end - - # - # Remove index file - # - def remove_index_file - Relaton::Index.find_or_create(:xsf, url: true, file: HitCollection::INDEX_FILE).remove_file - end - end -end diff --git a/lib/relaton_xsf/util.rb b/lib/relaton_xsf/util.rb deleted file mode 100644 index c6a102f..0000000 --- a/lib/relaton_xsf/util.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RelatonXsf - module Util - extend RelatonBib::Util - PROGNAME = "relaton-xsf".freeze - end -end diff --git a/lib/relaton_xsf/version.rb b/lib/relaton_xsf/version.rb deleted file mode 100644 index a31bfdf..0000000 --- a/lib/relaton_xsf/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module RelatonXsf - VERSION = "1.20.0" -end diff --git a/lib/relaton_xsf/xml_parser.rb b/lib/relaton_xsf/xml_parser.rb deleted file mode 100644 index f4dcf57..0000000 --- a/lib/relaton_xsf/xml_parser.rb +++ /dev/null @@ -1,14 +0,0 @@ -module RelatonXsf - class XMLParser < RelatonBib::XMLParser - # - # Create bibliographic item - # - # @param item_hash [Hash] bibliographic item hash - # - # @return [RelatonXsf::BibliographicItem] bibliographic item - # - def self.bib_item(item_hash) - BibliographicItem.new(**item_hash) - end - end -end diff --git a/relaton_xsf.gemspec b/relaton_xsf.gemspec index ab8bec2..506cfdd 100644 --- a/relaton_xsf.gemspec +++ b/relaton_xsf.gemspec @@ -1,20 +1,20 @@ # frozen_string_literal: true -require_relative "lib/relaton_xsf/version" +require_relative "lib/relaton/xsf/version" Gem::Specification.new do |spec| spec.name = "relaton-xsf" - spec.version = RelatonXsf::VERSION + spec.version = Relaton::Xsf::VERSION spec.authors = ["Ribose Inc."] spec.email = ["open.source@ribose.com"] - spec.summary = "RelatonIso: retrieve ISO Standards for bibliographic use " \ + spec.summary = "Relaton::Xsf: retrieve ISO Standards for bibliographic use " \ "using the IsoBibliographicItem model" - spec.description = "RelatonIso: retrieve ISO Standards for bibliographic use " \ + spec.description = "Relaton::Xsf: retrieve ISO Standards for bibliographic use " \ "using the IsoBibliographicItem model" spec.homepage = "https://github.com/relaton/relaton-xsf" spec.license = "BSD-2-Clause" - spec.required_ruby_version = ">= 2.7.0" + spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0") # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" @@ -37,7 +37,8 @@ Gem::Specification.new do |spec| # spec.add_dependency "example-gem", "~> 1.0" spec.add_dependency "mechanize", "~> 2.10" - spec.add_dependency "relaton-bib", "~> 1.20.0" + spec.add_dependency "relaton-bib", "~> 2.0.0" + spec.add_dependency "relaton-core", "~> 0.0.13" spec.add_dependency "relaton-index", "~> 0.2.0" # For more information and examples about making a new gem, check out our diff --git a/spec/fixtures/bibdata.xml b/spec/fixtures/bibdata.xml index 0502d7b..0dfb5af 100644 --- a/spec/fixtures/bibdata.xml +++ b/spec/fixtures/bibdata.xml @@ -1,17 +1,20 @@ - - 2023-11-08 - XMPP Extension Protocols + + 2026-03-04 + XMPP Extension Protocols http://xmpp.org/extensions/xep-0001.html + http://xmpp.org/extensions/xep-0001.html XEP 0001 - XEP-0001 XEP-0001 - 2024-03-11 + 2024-12-24 + + P. + Saint-Andre Peter Saint-Andre stpeter@stpeter.im @@ -21,6 +24,9 @@ + + D. + Cridland Dave Cridland dave@hellopando.com @@ -30,6 +36,9 @@ + + R. + Meijer Ralph Meijer ralphm@ik.nu @@ -38,10 +47,11 @@ en - XSF XEP + XSF XEP 0001 rfc + xsf - + \ No newline at end of file diff --git a/spec/fixtures/bibitem.xml b/spec/fixtures/bibitem.xml new file mode 100644 index 0000000..9902521 --- /dev/null +++ b/spec/fixtures/bibitem.xml @@ -0,0 +1,44 @@ + + 2023-11-08 + XMPP Extension Protocols + http://xmpp.org/extensions/xep-0001.html + XEP 0001 + XEP-0001 + XEP-0001 + + 2024-03-11 + + + + + + Peter Saint-Andre + + stpeter@stpeter.im + + + + + + + Dave Cridland + + dave@hellopando.com + + + + + + + Ralph Meijer + + ralphm@ik.nu + + + en + + + XSF XEP + 0001 + + diff --git a/spec/fixtures/index-v1.zip b/spec/fixtures/index-v1.zip new file mode 100644 index 0000000..b2798ab Binary files /dev/null and b/spec/fixtures/index-v1.zip differ diff --git a/spec/fixtures/item.yaml b/spec/fixtures/item.yaml new file mode 100644 index 0000000..e8b5f35 --- /dev/null +++ b/spec/fixtures/item.yaml @@ -0,0 +1,67 @@ +--- +id: XEP0001 +type: standard +schema_version: v1.5.6 +fetched: '2023-11-08' +title: +- language: en + script: Latn + content: XMPP Extension Protocols + format: text/plain +source: +- type: src + content: http://xmpp.org/extensions/xep-0001.html +docidentifier: +- content: XEP 0001 + type: XEP + primary: true +- content: XEP-0001 + type: XEP + scope: anchor +docnumber: XEP-0001 +date: +- type: published + at: '2024-03-11' +contributor: +- role: + - type: author + person: + email: + - stpeter@stpeter.im + name: + completename: + language: en + content: Peter Saint-Andre +- role: + - type: author + person: + email: + - dave@hellopando.com + name: + completename: + language: en + content: Dave Cridland +- role: + - type: author + person: + email: + - ralphm@ik.nu + name: + completename: + language: en + content: Ralph Meijer +language: +- en +script: +- Latn +series: +- type: main + title: + - language: en + script: Latn + content: XSF XEP + format: text/plain + number: '0001' +ext: + doctype: + content: rfc diff --git a/spec/relaton/xsf/bibdata_spec.rb b/spec/relaton/xsf/bibdata_spec.rb new file mode 100644 index 0000000..124780d --- /dev/null +++ b/spec/relaton/xsf/bibdata_spec.rb @@ -0,0 +1,9 @@ +describe Relaton::Xsf::Bibdata do + let(:file) { "spec/fixtures/bibdata.xml" } + let(:input_xml) { File.read file, encoding: "UTF-8" } + let(:item) { described_class.from_xml input_xml } + + it "round trip" do + expect(described_class.to_xml(item)).to be_equivalent_to input_xml + end +end diff --git a/spec/relaton/xsf/bibitem_spec.rb b/spec/relaton/xsf/bibitem_spec.rb new file mode 100644 index 0000000..e52333b --- /dev/null +++ b/spec/relaton/xsf/bibitem_spec.rb @@ -0,0 +1,9 @@ +describe Relaton::Xsf::Bibitem do + let(:file) { "spec/fixtures/bibitem.xml" } + let(:input_xml) { File.read file, encoding: "UTF-8" } + let(:item) { described_class.from_xml input_xml } + + it "round trip" do + expect(described_class.to_xml(item)).to be_equivalent_to input_xml + end +end diff --git a/spec/relaton_xsf/xsf_bibliography_spec.rb b/spec/relaton/xsf/bibliography_spec.rb similarity index 72% rename from spec/relaton_xsf/xsf_bibliography_spec.rb rename to spec/relaton/xsf/bibliography_spec.rb index e18bd33..ba62ce0 100644 --- a/spec/relaton_xsf/xsf_bibliography_spec.rb +++ b/spec/relaton/xsf/bibliography_spec.rb @@ -1,13 +1,13 @@ -describe RelatonXsf::Bibliography do +describe Relaton::Xsf::Bibliography do context "get" do it "successful", vcr: "get_successful" do expect do file = "spec/fixtures/bibdata.xml" - bib = RelatonXsf::Bibliography.get "XEP 0001" + bib = Relaton::Xsf::Bibliography.get "XEP 0001" xml = bib.to_xml bibdata: true File.write file, xml, encoding: "UTF-8" unless File.exist? file - expect(bib).to be_instance_of RelatonXsf::BibliographicItem - expect(bib.docidentifier.first.id).to eq "XEP 0001" + expect(bib).to be_instance_of Relaton::Bib::ItemData + expect(bib.docidentifier.first.content).to eq "XEP 0001" expect(xml).to be_equivalent_to File.read(file, encoding: "UTF-8").gsub( /(?<=)\d{4}-\d{2}-\d{2}/, Date.today.to_s ) @@ -18,7 +18,7 @@ end it "not found", vcr: "get_not_found" do - expect { RelatonXsf::Bibliography.get "XEP nope" }.to output( + expect { Relaton::Xsf::Bibliography.get "XEP nope" }.to output( /\[relaton-xsf\] INFO: \(XEP nope\) Not found\./, ).to_stderr_from_any_process end diff --git a/spec/relaton/xsf/data_fetcher_spec.rb b/spec/relaton/xsf/data_fetcher_spec.rb new file mode 100644 index 0000000..b4ed845 --- /dev/null +++ b/spec/relaton/xsf/data_fetcher_spec.rb @@ -0,0 +1,98 @@ +require "relaton/xsf/data_fetcher" + +describe Relaton::Xsf::DataFetcher do + context "instance methods" do + subject { described_class.new "data", "yaml" } + + it "index" do + expect(subject.index).to be_instance_of Relaton::Index::Type + end + + it "#fetch" do + agent = double "agent" + resp = Nokogiri::XML <<~XML + + +
+              reference.XSF.XEP-0001.xml
+            
+ + + XML + expect(agent).to receive(:get).with("https://xmpp.org/extensions/refs/").and_return resp + expect(Mechanize).to receive(:new).and_return agent + doc = double "doc", body: :body + expect(agent).to receive(:get).with("reference.XSF.XEP-0001.xml").and_return doc + expect(Relaton::Bib::Converter::BibXml).to receive(:to_item).with(:body).and_return :bib + expect(subject).to receive(:save_doc).with(:bib) + expect(subject.index).to receive(:save) + subject.fetch + end + + it "#fetch handles errors" do + agent = double "agent" + resp = Nokogiri::XML <<~XML + + +
+              reference.XSF.XEP-0001.xml
+            
+ + + XML + expect(Mechanize).to receive(:new).and_return agent + expect(agent).to receive(:get).with("https://xmpp.org/extensions/refs/").and_return resp + expect(agent).to receive(:get).with("reference.XSF.XEP-0001.xml").and_raise(StandardError, "connection error") + expect(subject).not_to receive(:save_doc) + expect(subject.index).to receive(:save) + expect { subject.fetch }.to output(/Failed to parse reference.XSF.XEP-0001.xml: connection error/).to_stderr_from_any_process + end + + context "#save_doc" do + let(:ext) { double("ext", flavor: nil) } + let(:bib) do + double "bibliographic item", + docidentifier: [double(content: "XEP-0001", primary: true)], + ext: ext, :"ext=" => nil + end + + before do + expect(ext).to receive(:flavor=).with("xsf") + expect(subject).to receive(:serialize).with(bib).and_return :yaml + expect(File).to receive(:write).with("data/xep-0001.yaml", :yaml, encoding: "UTF-8") + expect(subject.index).to receive(:add_or_update).with("XEP-0001", "data/xep-0001.yaml") + end + + it "no duplications" do + subject.save_doc bib + expect(subject.instance_variable_get(:@files)).to eq Set["data/xep-0001.yaml"] + end + + it "duplications" do + subject.instance_variable_set :@files, Set["data/xep-0001.yaml"] + expect { subject.save_doc bib }.to output( + /already exists/, + ).to_stderr_from_any_process + end + end + + context "#serialize" do + let(:bib) { double "bibliographic item" } + + it "yaml" do + expect(bib).to receive(:to_yaml).and_return :yaml + expect(subject.to_yaml(bib)).to eq :yaml + end + + it "xml" do + expect(bib).to receive(:to_xml).with(bibdata: true).and_return :xml + expect(subject.to_xml(bib)).to eq :xml + end + + it "bibxml" do + expect(bib).to receive(:to_rfcxml).and_return :bibxml + expect(subject.to_bibxml(bib)).to eq :bibxml + end + end + end +end diff --git a/spec/relaton/xsf/hit_collection_spec.rb b/spec/relaton/xsf/hit_collection_spec.rb new file mode 100644 index 0000000..5f980e3 --- /dev/null +++ b/spec/relaton/xsf/hit_collection_spec.rb @@ -0,0 +1,8 @@ +describe Relaton::Xsf::HitCollection do + subject(:collection) { Relaton::Xsf::HitCollection.new "XEP 0001" } + + it "raise Relaton::RequestError" do + expect(subject).to receive(:index).and_raise Timeout::Error, "timeout" + expect { subject.search }.to raise_error Relaton::RequestError, "timeout" + end +end diff --git a/spec/relaton/xsf/hit_spec.rb b/spec/relaton/xsf/hit_spec.rb new file mode 100644 index 0000000..90a10b6 --- /dev/null +++ b/spec/relaton/xsf/hit_spec.rb @@ -0,0 +1,10 @@ +describe Relaton::Xsf::Hit do + subject { Relaton::Xsf::Hit.new url: "https://example.com" } + + it "raises Relaton::RequestError" do + agent = double "agent" + expect(agent).to receive(:get).and_raise SocketError + expect(Mechanize).to receive(:new).and_return agent + expect { subject.item }.to raise_error Relaton::RequestError + end +end diff --git a/spec/relaton/xsf/item_spec.rb b/spec/relaton/xsf/item_spec.rb new file mode 100644 index 0000000..a56bc06 --- /dev/null +++ b/spec/relaton/xsf/item_spec.rb @@ -0,0 +1,10 @@ +describe Relaton::Xsf::Item do + let(:input_yaml) { File.read "spec/fixtures/item.yaml", encoding: "UTF-8" } + let(:input_hash) { YAML.safe_load input_yaml } + let(:item) { described_class.from_yaml input_yaml } + let(:output_hash) { YAML.safe_load described_class.to_yaml(item) } + + it "round trip" do + expect(output_hash).to eq input_hash + end +end diff --git a/spec/relaton/xsf/processor_spec.rb b/spec/relaton/xsf/processor_spec.rb new file mode 100644 index 0000000..99f7286 --- /dev/null +++ b/spec/relaton/xsf/processor_spec.rb @@ -0,0 +1,47 @@ +require "relaton/xsf/processor" +require "relaton/xsf/data_fetcher" + +describe Relaton::Xsf::Processor do + subject { described_class.new } + + it "has correct attributes" do + expect(subject.short).to eq :relaton_xsf + expect(subject.prefix).to eq "XEP" + expect(subject.defaultprefix).to eq %r{^XEP\s} + expect(subject.idtype).to eq "XEP" + expect(subject.datasets).to eq %w[xep-xmpp] + end + + it "#get" do + expect(Relaton::Xsf::Bibliography).to receive(:get).with("code", nil, {}).and_return :item + expect(subject.get("code", nil, {})).to eq :item + end + + it "#fetch_data" do + expect(Relaton::Xsf::DataFetcher).to receive(:fetch).with(output: "dir", format: "yaml").and_return :data + expect(subject.fetch_data("source", output: "dir", format: "yaml")).to eq :data + end + + it "#from_xml" do + expect(Relaton::Bib::Item).to receive(:from_xml).with("").and_return :item + expect(subject.from_xml("")).to eq :item + end + + it "#from_yaml" do + expect(Relaton::Bib::Item).to receive(:from_yaml).with("yaml").and_return :item + expect(subject.from_yaml("yaml")).to eq :item + end + + it "#grammar_hash" do + expect(subject.grammar_hash).to eq Relaton::Xsf.grammar_hash + end + + it "#remove_index_file" do + index = double "index" + expect(Relaton::Index).to receive(:find_or_create).with( + :xsf, url: true, file: "index-v1.yaml" + ).and_return index + expect(index).to receive(:remove_file) + subject.remove_index_file + end +end diff --git a/spec/relaton/xsf/xsf_spec.rb b/spec/relaton/xsf/xsf_spec.rb new file mode 100644 index 0000000..ac21d7e --- /dev/null +++ b/spec/relaton/xsf/xsf_spec.rb @@ -0,0 +1,5 @@ +describe Relaton::Xsf do + it ".grammar_hash" do + expect(described_class.grammar_hash).to eq Digest::MD5.hexdigest(Relaton::Bib::VERSION) + end +end diff --git a/spec/relaton_xsf/aml_parser_spec.rb b/spec/relaton_xsf/aml_parser_spec.rb deleted file mode 100644 index 0985558..0000000 --- a/spec/relaton_xsf/aml_parser_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -describe RelatonXsf::XMLParser do - it "returns RelatonXsf::BibliographicItem" do - xml = File.read "spec/fixtures/bibdata.xml", encoding: "UTF-8" - item = RelatonXsf::XMLParser.from_xml xml - expect(item).to be_instance_of RelatonXsf::BibliographicItem - expect(item.docidentifier.first.id).to eq "XEP 0001" - end -end diff --git a/spec/relaton_xsf/bibxml_parser_spec.rb b/spec/relaton_xsf/bibxml_parser_spec.rb deleted file mode 100644 index 7108cf2..0000000 --- a/spec/relaton_xsf/bibxml_parser_spec.rb +++ /dev/null @@ -1,7 +0,0 @@ -describe RelatonXsf::BibXMLParser do - it "returns bibliographic item" do - args = { docnumber: "XEP-0001", docstatus: "draft", doctype: "standard" } - expect(RelatonXsf::BibliographicItem).to receive(:new).with(args).and_return :bibitem - expect(described_class.bib_item(args)).to eq :bibitem - end -end diff --git a/spec/relaton_xsf/data_fetcher_spec.rb b/spec/relaton_xsf/data_fetcher_spec.rb deleted file mode 100644 index efc419f..0000000 --- a/spec/relaton_xsf/data_fetcher_spec.rb +++ /dev/null @@ -1,90 +0,0 @@ -describe RelatonXsf::DataFetcher do - it "initilizes" do - df = described_class.new "data", "bibxml" - expect(df.instance_variable_get(:@output)).to eq "data" - expect(df.instance_variable_get(:@format)).to eq "bibxml" - expect(df.instance_variable_get(:@ext)).to eq "xml" - expect(df.instance_variable_get(:@files)).to eq [] - end - - it "fetches data" do - df = double "data fetcher" - expect(df).to receive(:fetch) - expect(described_class).to receive(:new).with("data", "yaml").and_return df - described_class.fetch - end - - context "instance methods" do - subject { described_class.new "data", "yaml" } - - it "index" do - expect(subject.index).to be_instance_of Relaton::Index::Type - end - - it "#fetch" do - agent = double "agent" - resp = Nokogiri::XML <<~XML - - -
-              reference.XSF.XEP-0001.xml
-            
- - - XML - expect(agent).to receive(:get).with("https://xmpp.org/extensions/refs/").and_return resp - expect(Mechanize).to receive(:new).and_return agent - doc = double "doc", body: :body - expect(agent).to receive(:get).with("reference.XSF.XEP-0001.xml").and_return doc - expect(RelatonXsf::BibXMLParser).to receive(:parse).with(:body).and_return :bib - expect(subject).to receive(:write_doc).with(:bib) - expect(subject.index).to receive(:save) - subject.fetch - end - - context "#write_doc" do - let(:bib) { double "bibliographic item", docidentifier: [double(id: "XEP-0001", primary: true)] } - - before do - expect(subject).to receive(:serialize).with(bib).and_return :yaml - expect(File).to receive(:write).with("data/xep-0001.yaml", :yaml, encoding: "UTF-8") - expect(subject.index).to receive(:add_or_update).with("XEP-0001", "data/xep-0001.yaml") - end - - it "no duplications" do - subject.write_doc bib - expect(subject.instance_variable_get(:@files)).to eq ["data/xep-0001.yaml"] - end - - it "duplications" do - subject.instance_variable_set :@files, ["data/xep-0001.yaml"] - expect { subject.write_doc bib }.to output( - "\[relaton-xsf\] WARN: data/xep-0001.yaml already exists\n", - ).to_stderr_from_any_process - end - end - - context "#serialize" do - let(:bib) { double "bibliographic item" } - - it "yaml" do - hash = double "hash" - expect(bib).to receive(:to_hash).and_return hash - expect(hash).to receive(:to_yaml).and_return :yaml - expect(subject.serialize(bib)).to eq :yaml - end - - it "xml" do - subject.instance_variable_set :@format, "xml" - expect(bib).to receive(:to_xml).with(bibdata: true).and_return :xml - expect(subject.serialize(bib)).to eq :xml - end - - it "bibxml" do - subject.instance_variable_set :@format, "bibxml" - expect(bib).to receive(:to_bibxml).and_return :bibxml - expect(subject.serialize(bib)).to eq :bibxml - end - end - end -end diff --git a/spec/relaton_xsf/has_converter_spec.rb b/spec/relaton_xsf/has_converter_spec.rb deleted file mode 100644 index 1396309..0000000 --- a/spec/relaton_xsf/has_converter_spec.rb +++ /dev/null @@ -1,9 +0,0 @@ -describe RelatonXsf::HashConverter do - it "returns RelatonXsf::BibliographicItem" do - docid = RelatonBib::DocumentIdentifier.new(id: "XEP 0001", type: "XSF") - bib = RelatonXsf::HashConverter.bib_item docid: [docid] - expect(bib).to be_instance_of RelatonXsf::BibliographicItem - expect(bib.docidentifier.first).to be_instance_of RelatonBib::DocumentIdentifier - expect(bib.docidentifier.first.id).to eq "XEP 0001" - end -end diff --git a/spec/relaton_xsf/hit_collection_spec.rb b/spec/relaton_xsf/hit_collection_spec.rb deleted file mode 100644 index 437f536..0000000 --- a/spec/relaton_xsf/hit_collection_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -describe RelatonXsf::HitCollection do - subject(:collection) { RelatonXsf::HitCollection.new "XEP 0001" } - - it "raise RelatonBib::RequestError" do - expect(subject).to receive(:index).and_raise Timeout::Error, "timeout" - expect { subject.search }.to raise_error RelatonBib::RequestError, "timeout" - end -end diff --git a/spec/relaton_xsf/hit_spec.rb b/spec/relaton_xsf/hit_spec.rb deleted file mode 100644 index c7f25bd..0000000 --- a/spec/relaton_xsf/hit_spec.rb +++ /dev/null @@ -1,10 +0,0 @@ -describe RelatonXsf::Hit do - subject { RelatonXsf::Hit.new url: "https://example.com" } - - it "raises RelatonBib::RequestError" do - agent = double "agent" - expect(agent).to receive(:get).and_raise SocketError - expect(Mechanize).to receive(:new).and_return agent - expect { subject.fetch }.to raise_error RelatonBib::RequestError - end -end diff --git a/spec/relaton_xsf_spec.rb b/spec/relaton_xsf_spec.rb deleted file mode 100644 index 2ae71a4..0000000 --- a/spec/relaton_xsf_spec.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe RelatonXsf do - it "has a version number" do - expect(RelatonXsf::VERSION).not_to be nil - end - - # it "does something useful" do - # expect(false).to eq(true) - # end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e5d5e41..5167429 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,7 +2,7 @@ Dir.glob("#{__dir__}/support/**/*.rb").sort.each { |f| require f } -require "relaton_xsf" +require "relaton/xsf" RSpec.configure do |config| # Enable flags like --only-failures and --next-failure diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb index 1647fac..c10fe0a 100644 --- a/spec/support/vcr.rb +++ b/spec/support/vcr.rb @@ -10,4 +10,9 @@ } config.hook_into :webmock config.configure_rspec_metadata! + + # Index downloads are handled by pre-loaded fixtures in webmock.rb + config.ignore_request do |request| + URI(request.uri).path.end_with?("index-v1.zip") + end end diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb index 7826a22..dda41e1 100644 --- a/spec/support/webmock.rb +++ b/spec/support/webmock.rb @@ -1,6 +1,23 @@ require "webmock/rspec" +require "zip" +require "yaml" + +INDEX_ZIP_PATH = File.join(__dir__, "..", "fixtures", "index-v1.zip") RSpec.configure do |config| + config.before(:suite) do + yaml = Zip::File.open(INDEX_ZIP_PATH) do |zip| + zip.first.get_input_stream.read + end + index_data = YAML.safe_load(yaml, permitted_classes: [Symbol]) + + type = Relaton::Index::Type.new(:xsf, nil, "index-v1.yaml") + type.instance_variable_set(:@index, index_data) + type.define_singleton_method(:actual?) { |**args| args.key?(:url) } + + Relaton::Index.pool.instance_variable_get(:@pool)[:XSF] = type + end + config.before(:each) do WebMock.reset! WebMock.disable_net_connect! diff --git a/spec/vcr_cassettes/get_not_found.yml b/spec/vcr_cassettes/get_not_found.yml deleted file mode 100644 index bf6437d..0000000 --- a/spec/vcr_cassettes/get_not_found.yml +++ /dev/null @@ -1,130 +0,0 @@ ---- -http_interactions: -- request: - method: get - uri: https://raw.githubusercontent.com/relaton/relaton-data-xsf/main/index-v1.zip - body: - encoding: US-ASCII - base64_string: '' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: - - Ruby - response: - status: - code: 200 - message: OK - headers: - Connection: - - keep-alive - Content-Length: - - '2522' - Cache-Control: - - max-age=300 - Content-Security-Policy: - - default-src 'none'; style-src 'unsafe-inline'; sandbox - Content-Type: - - application/zip - Etag: - - W/"f3898d2a831807c4f91ae9f4bec6eb94e9efc4f5c3ca1d505dc938cc13a4a031" - Strict-Transport-Security: - - max-age=31536000 - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - deny - X-Xss-Protection: - - 1; mode=block - X-Github-Request-Id: - - 46E5:30FF9F:6CFA07:791A39:66A90E6F - Accept-Ranges: - - bytes - Date: - - Tue, 30 Jul 2024 16:01:51 GMT - Via: - - 1.1 varnish - X-Served-By: - - cache-iad-kjyo7100132-IAD - X-Cache: - - HIT - X-Cache-Hits: - - '1' - X-Timer: - - S1722355312.744380,VS0,VE1 - Vary: - - Authorization,Accept-Encoding,Origin - Access-Control-Allow-Origin: - - "*" - Cross-Origin-Resource-Policy: - - cross-origin - X-Fastly-Request-Id: - - 2f57541d370e64f14dc48d1a54ae215a61456782 - Expires: - - Tue, 30 Jul 2024 16:06:51 GMT - Source-Age: - - '0' - body: - encoding: ASCII-8BIT - base64_string: | - UEsDBBQAAAAIAGVy5Vic/jzyKgkAAPBUAAANABwAaW5kZXgtdjEueWFtbFVU - CQAD3gCIZt4AiGZ1eAsAAQTpAwAABH8AAABt2LsKLgYVhNE+T3Fe4OiZmf3f - 0gmmFMTKNpAIgoKIhb69Bjtdu9xM93Xr69ev33398v2ff/r+yx9/+P2Xb9++ - 5bsvX77/05//8vP3X3768R8//vqfP//t6y/vX/3rx7/+5X+29bbazttpe96e - tg9vH9o+vX1q+/L2pe3b27e2H28/2OYbt/mmrbtF3eJuUbe4W9Qt7hZ1i7tF - 3eJuUbe4W9Qt7hZ1i7tF3epuVbe6W9Wt7lZ1q7tV3epuVbe6W9Wt7lZ1q7tV - 3epuVbe6W9Vt7jZ1m7tN3eZuU7e529Rt7jZ1m7tN3eZuU7e529Rt7jZ1m7tN - 3c7dTt3O3U7dzt1O3c7dTt3O3U7dzt1O3c7dTt3O3U7dzt1O3c7dTt0e7vZQ - t4e7PdTt4W4PdXu420PdHu72ULeHuz3U7eFuD3V7uNtD3R7u9lC3h7s91O3p - bk91e7rbU92e7vZUt6e7PdXt6W5PdXu621Pdnu72VLenuz3V7eluT3V7uttT - 3V7u9lK3l7u91O3lbi91e7nbS91e7vZSt5e7vdTt5W4vdXu520vdXu72UreX - u73U7e1ub3V7u9tb3d7u9la3t7u91e3tbm91e7vbW93e7vZWt7e7vdXt7W5v - dXu721vdPu72UbePu33U7eNuH3X7uNtH3T7u9lG3j7t91O3jbh91+7jbR90+ - 7vZRt4+7fdAt39jtP29t2S3ykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTykthLIi+JvSTy - kthLIi+JvSTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTyktpLKi+pvaTy - ktpLKi+pvaTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTyktlLJi+ZvWTy - ktlLJi+ZvWTykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJS85e - cvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y8 - 5OwlJy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUnLzl7 - yclLzl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvOXnLy - krOXnLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5ecvOTs - JScvOXvJyUvOXnLykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJ - S85ecvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUnLzl7yclLzl5y8pKz - l5y85OwlJy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUn - Lzl7yclLzl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvO - XnLykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5cc - vOQPP/zmt7/7Aeu///zjT3/9+f/2//zPYf3L+7/bfwNQSwECHgMUAAAACABl - cuVYnP488ioJAADwVAAADQAYAAAAAAABAAAApIEAAAAAaW5kZXgtdjEueWFt - bFVUBQAD3gCIZnV4CwABBOkDAAAEfwAAAFBLBQYAAAAAAQABAFMAAABxCQAA - AAA= - recorded_at: Tue, 30 Jul 2024 16:01:51 GMT -recorded_with: VCR 6.2.0 diff --git a/spec/vcr_cassettes/get_successful.yml b/spec/vcr_cassettes/get_successful.yml index 3d38f70..ce78ec2 100644 --- a/spec/vcr_cassettes/get_successful.yml +++ b/spec/vcr_cassettes/get_successful.yml @@ -2,134 +2,7 @@ http_interactions: - request: method: get - uri: https://raw.githubusercontent.com/relaton/relaton-data-xsf/main/index-v1.zip - body: - encoding: US-ASCII - base64_string: '' - headers: - Accept-Encoding: - - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 - Accept: - - "*/*" - User-Agent: - - Ruby - response: - status: - code: 200 - message: OK - headers: - Connection: - - keep-alive - Content-Length: - - '2560' - Cache-Control: - - max-age=300 - Content-Security-Policy: - - default-src 'none'; style-src 'unsafe-inline'; sandbox - Content-Type: - - application/zip - Etag: - - W/"295e6d0033b88cabf49492fcd3148079227cd52244293601783a09092d2c5bca" - Strict-Transport-Security: - - max-age=31536000 - X-Content-Type-Options: - - nosniff - X-Frame-Options: - - deny - X-Xss-Protection: - - 1; mode=block - X-Github-Request-Id: - - C7EB:261529:37C99E:3D1D8E:6758D9EB - Accept-Ranges: - - bytes - Date: - - Wed, 11 Dec 2024 00:16:43 GMT - Via: - - 1.1 varnish - X-Served-By: - - cache-iad-kjyo7100093-IAD - X-Cache: - - MISS - X-Cache-Hits: - - '0' - X-Timer: - - S1733876204.680586,VS0,VE116 - Vary: - - Authorization,Accept-Encoding,Origin - Access-Control-Allow-Origin: - - "*" - Cross-Origin-Resource-Policy: - - cross-origin - X-Fastly-Request-Id: - - bd7b3eaccb32af78f539df808ddbcb454f3557d4 - Expires: - - Wed, 11 Dec 2024 00:21:43 GMT - Source-Age: - - '0' - body: - encoding: ASCII-8BIT - base64_string: | - UEsDBBQAAAAIAI9yg1mjn7tiUAkAAFBWAAANABwAaW5kZXgtdjEueWFtbFVU - CQADrhNPZ64TT2d1eAsAAQTpAwAABH8AAABt2LsKLgYVhNE+T3Fe4OjZM/Pf - 0gmmFMTKNpAIgoKIhb69Bjtdu9xM93Xr69ev33398v2ff/r+yx9/+P2Xb9++ - 3Xdfvnz/pz//5efvv/z04z9+/PU/f/7b11/ev/rXj3/9y/9s4220rbfVdt5O - 24e3D22f3j61fXn70vbt7Vvbj7cfbO8bt/dNW3c7dTt3O3U7dzt1O3c7dTt3 - O3U7dzt1O3c7dTt3O3U7dzt1i7tF3eJuUbe4W9Qt7hZ1i7tF3eJuUbe4W9Qt - 7hZ1i7tF3eJuUbe6W9Wt7lZ1q7tV3epuVbe6W9Wt7lZ1q7tV3epuVbe6W9Wt - 7lZ1m7tN3eZuU7e529Rt7jZ1m7tN3eZuU7e529Rt7jZ1m7tN3eZuU7eHuz3U - 7eFuD3V7uNtD3R7u9lC3h7s91O3hbg91e7jbQ90e7vZQt4e7PdTt4W4PdXu6 - 21Pdnu72VLenuz3V7eluT3V7uttT3Z7u9lS3p7s91e3pbk91e7rbU92e7vZU - t5e7vdTt5W4vdXu520vdXu72UreXu73U7eVuL3V7udtL3V7u9lK3l7u91O3l - bi91e7vbW93e7vZWt7e7vdXt7W5vdXu721vd3u72Vre3u73V7e1ub3V7u9tb - 3d7u9la3j7t91O3jbh91+7jbR90+7vZRt4+7fdTt424fdfu420fdPu72UbeP - u33U7eNuH3S7b+z2n7e27HbykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y85Owl - Jy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUnLzl7yclL - zl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvOXnLykrOX - nLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5ecvOTsJScv - OXvJyUvOXnLykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJS85e - cvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y8 - 5OwlJy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUnLzl7 - yclLzl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5ecvOTsJScvOXvJyUvOXnLy - krOXnLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJS85ecvKSs5ecvOTs - JScvOXvJyUvOXnLykrOXnLzk7CUnLzl7yclLzl5y8pKzl5y85OwlJy85e8nJ - S85ecvKSs5ecvOTsJScvOXvJyUvOXnLykrOXnLzk7CUnL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9 - JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL4m9JPKS2EsiL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9 - pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL6m9pPKS2ksqL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9 - ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL5m9ZPKS2UsmL3nYSx7wkj/88Jvf - /u4HrP/+848//fXn/9v/8z+H9S/v/27/DVBLAQIeAxQAAAAIAI9yg1mjn7ti - UAkAAFBWAAANABgAAAAAAAEAAACkgQAAAABpbmRleC12MS55YW1sVVQFAAOu - E09ndXgLAAEE6QMAAAR/AAAAUEsFBgAAAAABAAEAUwAAAJcJAAAAAA== - recorded_at: Wed, 11 Dec 2024 00:16:43 GMT -- request: - method: get - uri: https://raw.githubusercontent.com/relaton/relaton-data-xsf/main/data/xep-0001.yaml + uri: https://raw.githubusercontent.com/relaton/relaton-data-xsf/data-v2/data/xep-0001.yaml body: encoding: US-ASCII base64_string: '' @@ -139,7 +12,7 @@ http_interactions: Accept: - "*/*" User-Agent: - - Mechanize/2.12.2 Ruby/3.3.3p89 (http://github.com/sparklemotion/mechanize/) + - Mechanize/2.14.0 Ruby/3.4.7p58 (http://github.com/sparklemotion/mechanize/) Accept-Language: - en-us,en;q=0.5 Host: @@ -156,7 +29,7 @@ http_interactions: Connection: - keep-alive Content-Length: - - '541' + - '499' Cache-Control: - max-age=300 Content-Security-Policy: @@ -164,7 +37,7 @@ http_interactions: Content-Type: - text/plain; charset=utf-8 Etag: - - W/"1efe49c4f477584fb1be5e0f3dc7dd173c9889b51f1e78b10ecd3a0fd1dac940" + - W/"2ab0fbd0f01df4410511b9c45914c4e6d162be711f9ebef6cfee0a32ee86504e" Strict-Transport-Security: - max-age=31536000 X-Content-Type-Options: @@ -174,50 +47,49 @@ http_interactions: X-Xss-Protection: - 1; mode=block X-Github-Request-Id: - - 6956:277BC6:4D5A2:56EF9:6758D9E9 + - BD3C:188D9C:62D735:780502:69D920C5 Content-Encoding: - gzip Accept-Ranges: - bytes Date: - - Wed, 11 Dec 2024 00:16:44 GMT + - Fri, 10 Apr 2026 16:09:42 GMT Via: - 1.1 varnish X-Served-By: - - cache-iad-kjyo7100033-IAD + - cache-iad-kcgs7200108-IAD X-Cache: - MISS X-Cache-Hits: - '0' X-Timer: - - S1733876204.939085,VS0,VE100 + - S1775837382.003589,VS0,VE110 Vary: - - Authorization,Accept-Encoding,Origin + - Authorization,Accept-Encoding Access-Control-Allow-Origin: - "*" Cross-Origin-Resource-Policy: - cross-origin X-Fastly-Request-Id: - - 635719b6a2bd68df5f0aed51fc8378a52bad3b98 + - f53602e08387a5bbf8011b679b916804fe62226d Expires: - - Wed, 11 Dec 2024 00:21:44 GMT + - Fri, 10 Apr 2026 16:14:42 GMT Source-Age: - '0' body: encoding: ASCII-8BIT base64_string: | - H4sIAAAAAAAAA81VuY7bMBDt+RXsXEk+kiaqHMSbKgsI3ma7gKZmLWZ5YUgJ - 3r/PUJJlObCBCDCQqBE5xxsOHzEvyzIWZA1GZC1gUM4WvF3nm/wLU1XBX5/K - 1Wq1ZlFFDQXLuHQ2go3keS5L/nSiTUriJbropNOBca6FPTbiSPGcZxws/YJE - 5WNv+CFiMr05NIKAIpzi0muhLNPKvl8VqWP0xXJ5Mt7nDo9LONcLyxP4LB0t - r6PRBBc/PBQ8oGTDKgpbCaxY5SR1QqhDP7xr6JxABlp7VEbgBx0GG7iEZjdC - g3RpI6ysHSZw25gD4CS+ErG7qj7LNwetQg0V5bZCN2RabFabz9nqU7ZeL1jq - FdWhiQ5TkicSiAMK5twKA/2K86NqwZ433eXB1J3udby10XZNxSWyo+T8Kaui - Errg5RSdqIlQ/RycYZo/1inzv68UGrw+74jyQszH7KutENh9sAmUdMZriHAH - ryQXzkVN2ULG3kNWI5ROb8gnsO3wz5UhPzo9vOyeYNHE9BL+KXW7udTtHkLd - N1QVJVcP4W0nWpiFeJuzimC2NWjtPOG4nKr+p6Tt55K2fwhpz6B+AT6Esr3Q - vp4BeJsxTChmq95z29ynCqHtBuv19LwU64qcZWYQmQCoIFxmsUkiQ+O8F7Or - Vl5fvg8D/s8OxtNfNGyiYjd1jJ7SIAqLpAiLJBPdCUYtwTfJfgM1laV7ewcA - AA== - recorded_at: Wed, 11 Dec 2024 00:16:43 GMT -recorded_with: VCR 6.3.1 + H4sIAAAAAAAAA6WUPW/bMBCGd/4Kbp4kf8CTJhe1iwwxIDgdsgW0eLLY8AtH + ynD+fY+yZCttgKKWF/HEe9+jHsJvlmVMyYK/7srFYrFk8cNDwUMUVgqULFQN + GPF2BgzK2YKfl/k6py4VNRQs41rYUytOJAHLOA8VKh8L/ixiKitnI1iqX/dl + yXcXKpINL9FFVzkdWHAtVp1TPxirsa6J0Rfz+cV4nzs8zWGwCPML+CydOG+i + 0Tf908/980MG0lVKkkTVCjCd5372Xck7NLyfQS9o7VEZgR8Fj9hCktvWHEma + tjtfJkUcfZlvj1qFBiRpBdnOVovVOluustV6xtIwVMc2um42ukSX80Er2tg4 + TEPpHugaaMU53YvS12VGF+YhAm76Z65Mt2GFgWsL57VDGNd/Xd71p6yKSuiC + l3edETGCfOu3QnFr/tLhRq7M+3ehxfHkf+lehLIx+2YlAhu2jNf0Yf/jUiYQ + n7weASvFGTYNaO08/SVcTieZTHY7mez2QbLfUUnqkBOwbgnI3ecRpCi0b8xG + vee2nczyMJnl4UGWe1C/ACeQPCQOg82tlZBScx+kVHRRGgAVhHuYEM5k2Mfw + l5T+iOJxGL/86DNsiKxZyqsZo3BMZpRl3ZTPKqxTMtdanCmj+CXU7DfV35fK + OwYAAA== + recorded_at: Fri, 10 Apr 2026 16:09:42 GMT +recorded_with: VCR 6.4.0