main
rb 79 lines 3.09 KB
Raw
1 # frozen_string_literal: true
2
3 require "rails_helper"
4
5 # The SEO/social meta tags must be in the initial server HTML — crawlers don't
6 # run JS — and must never expose anything about a private repo.
7 RSpec.describe "Repository SEO meta tags", type: :request do
8 let(:owner) { User.create!(smbcloud_id: 5001, email: "alice@example.com", username: "alice") }
9 let(:other) { User.create!(smbcloud_id: 5002, email: "bob@example.com", username: "bob") }
10
11 let!(:public_repo) do
12 owner.repositories.create!(
13 name: "widget", description: "A tiny widget library for building UIs fast.",
14 kind: "code", default_branch: "main",
15 disk_path: "/nonexistent/alice/widget.git", is_private: false, stars_count: 7
16 )
17 end
18
19 let!(:private_repo) do
20 other.repositories.create!(
21 name: "secret-internal-tool", description: "TOPSECRETDESCRIPTION should never leak to crawlers.",
22 kind: "code", default_branch: "main",
23 disk_path: "/nonexistent/bob/secret.git", is_private: true
24 )
25 end
26
27 describe "a public repo page" do
28 before { get "/alice/widget" }
29
30 it "responds 200" do
31 expect(response).to have_http_status(:ok)
32 end
33
34 it "emits Open Graph, Twitter, and canonical tags" do
35 expect(response.body).to include('property="og:title" content="alice/widget"')
36 expect(response.body).to include('property="og:site_name" content="siGit"')
37 expect(response.body).to include('property="og:image" content="http://www.example.com/og/alice/widget.png"')
38 expect(response.body).to include('property="og:image:width" content="1200"')
39 expect(response.body).to include('property="og:image:height" content="630"')
40 expect(response.body).to include('name="twitter:card" content="summary_large_image"')
41 expect(response.body).to include('rel="canonical" href="http://www.example.com/alice/widget"')
42 expect(response.body).to include("A tiny widget library")
43 end
44
45 it "emits SoftwareSourceCode JSON-LD" do
46 expect(response.body).to include('"@type":"SoftwareSourceCode"')
47 expect(response.body).to include('"codeRepository":"http://www.example.com/alice/widget"')
48 end
49
50 it "is indexable" do
51 expect(response.body).to include('name="robots" content="index, follow"')
52 end
53 end
54
55 it "still emits a non-empty description when the repo has none" do
56 public_repo.update_column(:description, nil)
57 get "/alice/widget"
58 expect(response).to have_http_status(:ok)
59 expect(response.body).to include("Git hosting built for AI workflows")
60 end
61
62 describe "a private repo" do
63 it "404s and leaks no metadata to a logged-out crawler" do
64 get "/bob/secret-internal-tool"
65 expect(response).to have_http_status(:not_found)
66 expect(response.body).not_to include("TOPSECRETDESCRIPTION")
67 expect(response.body).not_to include("secret-internal-tool")
68 expect(response.body).not_to include("og/bob")
69 end
70 end
71
72 describe "auth pages" do
73 it "are marked noindex" do
74 get "/auth"
75 expect(response).to have_http_status(:ok)
76 expect(response.body).to include('name="robots" content="noindex, nofollow"')
77 end
78 end
79 end