opengraphplus 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8fd514b98d00901b6c41cf42ad246f71690c7fb632c2653f1e66571da1c0968
4
- data.tar.gz: cb7eef25110066ade4cf4b5f05fe5f10448c7e3cf8a9f6d5bb91b785f7687d37
3
+ metadata.gz: 3ed71a686aba73ab87adf5879a2ad3cbf6e590eec37ce7a84f0f5db2859c924e
4
+ data.tar.gz: 2484bbb094455d512d68ada748c07d9baeeabee0d5072104753443ceb86b27c1
5
5
  SHA512:
6
- metadata.gz: 2f0bb3269f68f0b57ab02e9f583042bae211f5c2f0ca7dec28a7cd35fed2e9e32ac934241380ce856f88a9919dfb2b4bdc8bbc46d2a87d70a2c6694207913a6e
7
- data.tar.gz: 0b77487f2356101b48750cb729f9b88327678aafabe02fa5da16dc9ea55f2c0d5e5a3a3351ddb1294af14d791e7495fa2012ea82b4cac4afadc90f6d8e4b8770
6
+ metadata.gz: 1a8a858d5b2e4c4f5ed06b066ffe0f369dadee0ac5e5f3c1590e1f5495d876e39024d463b3dcc1f392501088ead4f11825988b3437f84544e3311f2ed047e5ec
7
+ data.tar.gz: ccf697e5805ca290db0cc3e7ddd0a700b18db2671591db50a5f894d08acd0681f67c629a7fd2d94a8ec39311c97fba6002f6bbe5221c785991ce6150af7ee87a
data/README.md CHANGED
@@ -68,6 +68,112 @@ OpenGraphPlus.configure do |config|
68
68
  end
69
69
  ```
70
70
 
71
+ ## Verifying OpenGraph Tags
72
+
73
+ You can verify that your pages have the required OpenGraph tags using the included command:
74
+
75
+ ```bash
76
+ rails opengraph:verify http://localhost:3000
77
+ ```
78
+
79
+ This will fetch the URL and check for required tags (`og:title`, `og:type`, `og:image`, `og:url`), displaying any missing required or recommended tags:
80
+
81
+ ```
82
+ Verifying OpenGraph tags at http://localhost:3000...
83
+
84
+ Found tags:
85
+ og:title → "My Awesome Site"
86
+ og:type → "website"
87
+ og:image → "https://example.com/image.png"
88
+ og:url → "http://localhost:3000"
89
+
90
+ ✓ All required OpenGraph tags present
91
+ ```
92
+
93
+ The command exits with code 0 on success, or 1 if required tags are missing.
94
+
95
+ ## Testing
96
+
97
+ OpenGraphPlus provides test helpers for both RSpec and Minitest to verify OpenGraph tags in your test suite.
98
+
99
+ ### RSpec
100
+
101
+ Add to your `spec/spec_helper.rb` or `spec/rails_helper.rb`:
102
+
103
+ ```ruby
104
+ require 'opengraphplus/rspec'
105
+ ```
106
+
107
+ Then use the matchers in your specs:
108
+
109
+ ```ruby
110
+ RSpec.describe "Home page", type: :request do
111
+ it "has all required OpenGraph tags" do
112
+ get "/"
113
+ expect(response.body).to have_open_graph_tags
114
+ end
115
+
116
+ it "has the correct title" do
117
+ get "/"
118
+ expect(response.body).to have_og_tag("og:title").with_content("My Site")
119
+ end
120
+
121
+ it "has an og:image tag" do
122
+ get "/"
123
+ expect(response.body).to have_og_tag("og:image")
124
+ end
125
+ end
126
+ ```
127
+
128
+ Available matchers:
129
+ - `have_open_graph_tags` - Passes if all required OG tags are present
130
+ - `have_og_tag("og:title")` - Passes if the specified tag exists
131
+ - `have_og_tag("og:title").with_content("My Title")` - Passes if the tag has the expected content
132
+
133
+ ### Minitest
134
+
135
+ Add to your test helper:
136
+
137
+ ```ruby
138
+ require 'opengraphplus/minitest'
139
+
140
+ class ActiveSupport::TestCase
141
+ include OpenGraphPlus::Minitest
142
+ end
143
+ ```
144
+
145
+ Then use the assertions in your tests:
146
+
147
+ ```ruby
148
+ class HomePageTest < ActionDispatch::IntegrationTest
149
+ test "has all required OpenGraph tags" do
150
+ get "/"
151
+ assert_open_graph_tags(response.body)
152
+ end
153
+
154
+ test "has the correct title" do
155
+ get "/"
156
+ assert_og_tag(response.body, "og:title", "My Site")
157
+ end
158
+
159
+ test "has an og:image tag" do
160
+ get "/"
161
+ assert_og_tag(response.body, "og:image")
162
+ end
163
+
164
+ test "does not have private tags" do
165
+ get "/"
166
+ refute_og_tag(response.body, "og:private")
167
+ end
168
+ end
169
+ ```
170
+
171
+ Available assertions:
172
+ - `assert_open_graph_tags(html)` - Passes if all required OG tags are present
173
+ - `assert_og_tag(html, "og:title")` - Passes if the specified tag exists
174
+ - `assert_og_tag(html, "og:title", "My Title")` - Passes if the tag has the expected content
175
+ - `refute_og_tag(html, "og:private")` - Passes if the specified tag does not exist
176
+
71
177
  ## Development
72
178
 
73
179
  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.
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opengraphplus"
4
+
5
+ module OpenGraphPlus
6
+ module Minitest
7
+ # Assert that all required OpenGraph tags are present
8
+ #
9
+ # @param html [String] The HTML to check
10
+ # @param message [String, nil] Custom failure message
11
+ #
12
+ # @example
13
+ # assert_open_graph_tags(response.body)
14
+ #
15
+ def assert_open_graph_tags(html, message = nil)
16
+ parser = OpenGraphPlus::Parser.new(html)
17
+ message ||= "Expected HTML to have all required OpenGraph tags, but missing: #{parser.errors.join(', ')}"
18
+ assert parser.valid?, message
19
+ end
20
+
21
+ # Assert that a specific OpenGraph/Twitter tag is present
22
+ #
23
+ # @param html [String] The HTML to check
24
+ # @param property [String] The tag property (e.g., "og:title")
25
+ # @param content [String, nil] Expected content (optional)
26
+ # @param message [String, nil] Custom failure message
27
+ #
28
+ # @example
29
+ # assert_og_tag(response.body, "og:title")
30
+ # assert_og_tag(response.body, "og:title", "My Title")
31
+ #
32
+ def assert_og_tag(html, property, content = nil, message = nil)
33
+ parser = OpenGraphPlus::Parser.new(html)
34
+ actual_content = parser[property]
35
+
36
+ if content.nil?
37
+ message ||= "Expected HTML to have #{property} tag, but it was not found"
38
+ assert !actual_content.nil?, message
39
+ else
40
+ message ||= "Expected #{property} to have content #{content.inspect}, but got #{actual_content.inspect}"
41
+ assert_equal content, actual_content, message
42
+ end
43
+ end
44
+
45
+ # Assert that a specific OpenGraph/Twitter tag is NOT present
46
+ #
47
+ # @param html [String] The HTML to check
48
+ # @param property [String] The tag property (e.g., "og:title")
49
+ # @param message [String, nil] Custom failure message
50
+ #
51
+ # @example
52
+ # refute_og_tag(response.body, "og:private")
53
+ #
54
+ def refute_og_tag(html, property, message = nil)
55
+ parser = OpenGraphPlus::Parser.new(html)
56
+ actual_content = parser[property]
57
+ message ||= "Expected HTML not to have #{property} tag, but it was found with content #{actual_content.inspect}"
58
+ assert actual_content.nil?, message
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+
5
+ module OpenGraphPlus
6
+ class Parser
7
+ REQUIRED_TAGS = %w[og:title og:type og:image og:url].freeze
8
+ RECOMMENDED_TAGS = %w[og:description og:site_name twitter:card twitter:title twitter:image].freeze
9
+
10
+ attr_reader :html
11
+
12
+ def initialize(html)
13
+ @html = html.to_s
14
+ @tags = parse_meta_tags
15
+ end
16
+
17
+ def og_tags
18
+ @tags.select { |key, _| key.start_with?("og:") }
19
+ end
20
+
21
+ def twitter_tags
22
+ @tags.select { |key, _| key.start_with?("twitter:") }
23
+ end
24
+
25
+ def all_tags
26
+ @tags.dup
27
+ end
28
+
29
+ def [](property)
30
+ @tags[property]
31
+ end
32
+
33
+ def valid?
34
+ errors.empty?
35
+ end
36
+
37
+ def errors
38
+ REQUIRED_TAGS.reject { |tag| @tags.key?(tag) }
39
+ end
40
+
41
+ def warnings
42
+ RECOMMENDED_TAGS.reject { |tag| @tags.key?(tag) }
43
+ end
44
+
45
+ private
46
+
47
+ def parse_meta_tags
48
+ doc = Nokogiri::HTML(@html)
49
+ tags = {}
50
+
51
+ doc.css("meta[property^='og:'], meta[name^='og:']").each do |meta|
52
+ property = meta["property"] || meta["name"]
53
+ content = meta["content"]
54
+ tags[property] = content if property && content
55
+ end
56
+
57
+ doc.css("meta[property^='twitter:'], meta[name^='twitter:']").each do |meta|
58
+ property = meta["property"] || meta["name"]
59
+ content = meta["content"]
60
+ tags[property] = content if property && content
61
+ end
62
+
63
+ tags
64
+ end
65
+ end
66
+ end
@@ -15,7 +15,9 @@ module OpenGraphPlus
15
15
  end
16
16
 
17
17
  def open_graph_meta_tags
18
- open_graph.render_in(self)
18
+ @open_graph_root ||= Namespace::Root.new
19
+ yield @open_graph_root if block_given?
20
+ @open_graph_root.render_in(self)
19
21
  end
20
22
  end
21
23
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "opengraphplus"
4
+ require "rspec/expectations"
5
+
6
+ module OpenGraphPlus
7
+ module RSpec
8
+ extend ::RSpec::Matchers::DSL
9
+
10
+ # Matches if all required OpenGraph tags are present
11
+ #
12
+ # @example
13
+ # expect(response.body).to have_open_graph_tags
14
+ #
15
+ matcher :have_open_graph_tags do
16
+ match do |html|
17
+ @parser = OpenGraphPlus::Parser.new(html)
18
+ @parser.valid?
19
+ end
20
+
21
+ failure_message do
22
+ "expected HTML to have all required OpenGraph tags, but missing: #{@parser.errors.join(', ')}"
23
+ end
24
+
25
+ failure_message_when_negated do
26
+ "expected HTML not to have all required OpenGraph tags, but all were present"
27
+ end
28
+ end
29
+
30
+ # Matches if a specific OpenGraph/Twitter tag is present
31
+ #
32
+ # @example
33
+ # expect(response.body).to have_og_tag("og:title")
34
+ # expect(response.body).to have_og_tag("og:title").with_content("My Title")
35
+ #
36
+ matcher :have_og_tag do |property|
37
+ chain :with_content do |expected_content|
38
+ @expected_content = expected_content
39
+ end
40
+
41
+ match do |html|
42
+ @parser = OpenGraphPlus::Parser.new(html)
43
+ @actual_content = @parser[property]
44
+
45
+ if @actual_content.nil?
46
+ false
47
+ elsif @expected_content
48
+ @actual_content == @expected_content
49
+ else
50
+ true
51
+ end
52
+ end
53
+
54
+ failure_message do
55
+ if @actual_content.nil?
56
+ "expected HTML to have #{property} tag, but it was not found"
57
+ else
58
+ "expected #{property} to have content #{@expected_content.inspect}, but got #{@actual_content.inspect}"
59
+ end
60
+ end
61
+
62
+ failure_message_when_negated do
63
+ if @expected_content
64
+ "expected #{property} not to have content #{@expected_content.inspect}, but it did"
65
+ else
66
+ "expected HTML not to have #{property} tag, but it was found with content #{@actual_content.inspect}"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ RSpec.configure do |config|
74
+ config.include OpenGraphPlus::RSpec
75
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+
6
+ module OpenGraphPlus
7
+ class Verifier
8
+ class FetchError < Error; end
9
+
10
+ MAX_REDIRECTS = 5
11
+
12
+ attr_reader :url
13
+
14
+ def initialize(url)
15
+ @url = url
16
+ end
17
+
18
+ def verify
19
+ html = fetch_html
20
+ Parser.new(html)
21
+ end
22
+
23
+ private
24
+
25
+ def fetch_html(redirect_count = 0)
26
+ raise FetchError, "Too many redirects" if redirect_count > MAX_REDIRECTS
27
+
28
+ uri = URI.parse(@url)
29
+ response = Net::HTTP.get_response(uri)
30
+
31
+ case response
32
+ when Net::HTTPSuccess
33
+ response.body
34
+ when Net::HTTPRedirection
35
+ @url = response["location"]
36
+ fetch_html(redirect_count + 1)
37
+ else
38
+ raise FetchError, "HTTP #{response.code}: #{response.message}"
39
+ end
40
+ rescue URI::InvalidURIError => e
41
+ raise FetchError, "Invalid URL: #{e.message}"
42
+ rescue SocketError, Errno::ECONNREFUSED, Errno::ETIMEDOUT => e
43
+ raise FetchError, "Connection failed: #{e.message}"
44
+ end
45
+ end
46
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OpenGraphPlus
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.8"
5
5
  end
data/lib/opengraphplus.rb CHANGED
@@ -1,15 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "opengraphplus/version"
4
+
5
+ module OpenGraphPlus
6
+ class Error < StandardError; end
7
+ end
8
+
4
9
  require_relative "opengraphplus/api_key"
5
10
  require_relative "opengraphplus/configuration"
6
11
  require_relative "opengraphplus/signature"
7
12
  require_relative "opengraphplus/tag"
8
13
  require_relative "opengraphplus/namespace"
9
14
  require_relative "opengraphplus/image_generator"
10
-
11
- module OpenGraphPlus
12
- class Error < StandardError; end
13
- end
15
+ require_relative "opengraphplus/parser"
16
+ require_relative "opengraphplus/verifier"
14
17
 
15
18
  require_relative "opengraphplus/rails" if defined?(::Rails::Railtie)
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/command"
4
+
5
+ module Rails
6
+ module Command
7
+ class OpengraphCommand < Base
8
+ namespace "opengraph"
9
+
10
+ desc "verify URL", "Verify OpenGraph tags at a URL"
11
+ def verify(url)
12
+ require "opengraphplus"
13
+
14
+ say "Verifying OpenGraph tags at #{url}..."
15
+ say ""
16
+
17
+ begin
18
+ parser = OpenGraphPlus::Verifier.new(url).verify
19
+ rescue OpenGraphPlus::Verifier::FetchError => e
20
+ say_error e.message
21
+ exit 1
22
+ end
23
+
24
+ tags = parser.all_tags
25
+
26
+ if tags.any?
27
+ say "Found tags:"
28
+ max_key_length = tags.keys.map(&:length).max
29
+ tags.each do |property, content|
30
+ say " #{property.ljust(max_key_length)} → #{content.inspect}"
31
+ end
32
+ say ""
33
+ else
34
+ say "No OpenGraph or Twitter tags found."
35
+ say ""
36
+ end
37
+
38
+ if parser.errors.any?
39
+ say "Missing required tags:"
40
+ parser.errors.each do |tag|
41
+ say " ✗ #{tag}"
42
+ end
43
+ say ""
44
+ end
45
+
46
+ if parser.warnings.any?
47
+ say "Missing recommended tags:"
48
+ parser.warnings.each do |tag|
49
+ say " - #{tag}"
50
+ end
51
+ say ""
52
+ end
53
+
54
+ if parser.valid?
55
+ say "✓ All required OpenGraph tags present"
56
+ else
57
+ say "Verification failed: #{parser.errors.length} required tag(s) missing"
58
+ exit 1
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: opengraphplus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Gessler
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-01-08 00:00:00.000000000 Z
10
+ date: 2026-01-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -47,7 +47,9 @@ files:
47
47
  - lib/opengraphplus/api_key.rb
48
48
  - lib/opengraphplus/configuration.rb
49
49
  - lib/opengraphplus/image_generator.rb
50
+ - lib/opengraphplus/minitest.rb
50
51
  - lib/opengraphplus/namespace.rb
52
+ - lib/opengraphplus/parser.rb
51
53
  - lib/opengraphplus/rails.rb
52
54
  - lib/opengraphplus/rails/controller.rb
53
55
  - lib/opengraphplus/rails/helper.rb
@@ -55,13 +57,16 @@ files:
55
57
  - lib/opengraphplus/rails/signature.rb
56
58
  - lib/opengraphplus/rails/signature/routes.rb
57
59
  - lib/opengraphplus/rails/signature/scope.rb
60
+ - lib/opengraphplus/rspec.rb
58
61
  - lib/opengraphplus/signature.rb
59
62
  - lib/opengraphplus/signature/generator.rb
60
63
  - lib/opengraphplus/signature/url.rb
61
64
  - lib/opengraphplus/signature/verifier.rb
62
65
  - lib/opengraphplus/tag.rb
63
66
  - lib/opengraphplus/tags.rb
67
+ - lib/opengraphplus/verifier.rb
64
68
  - lib/opengraphplus/version.rb
69
+ - lib/rails/commands/opengraph/opengraph_command.rb
65
70
  - sig/open_graph_plus.rbs
66
71
  homepage: https://opengraphplus.com/ruby
67
72
  licenses: