Skip to content

Commit 1cdb0c1

Browse files
committed
test/docs: Add tests and documentation for new API endpoints
Tests: - search_config_spec.rb (NEW): Tests for /search/config endpoint - contact_spec.rb (NEW): Tests for /contact general enquiry endpoint - properties_spec.rb: Added tests for map_markers, pagination meta, highlighted filter, limit parameter, and locale support Documentation: - docs/api/01_rest_api.md: Added comprehensive Public API section documenting all headless frontend endpoints including properties, search config, site details, theme, pages, enquiries, contact, testimonials with example request/response formats
1 parent 93d2492 commit 1cdb0c1

File tree

4 files changed

+277
-0
lines changed

4 files changed

+277
-0
lines changed

docs/api/01_rest_api.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,102 @@ The website endpoint is used to manage the website's configuration.
145145

146146
---
147147

148+
## Public API (Headless Frontend)
149+
150+
The Public API (`/api_public/v1/`) is designed for headless JavaScript frontends (Astro.js, Next.js, etc.) and does not require authentication.
151+
152+
### Properties
153+
154+
**`GET /api_public/v1/properties`** - Search properties
155+
156+
Query Parameters:
157+
- `sale_or_rental` - "sale" or "rent"
158+
- `property_type` - Filter by property type
159+
- `for_sale_price_from/till` - Price range for sale
160+
- `for_rent_price_from/till` - Price range for rent
161+
- `bedrooms_from` - Minimum bedrooms
162+
- `bathrooms_from` - Minimum bathrooms
163+
- `highlighted` - "true" to filter featured properties
164+
- `limit` - Limit results
165+
- `page` - Page number (default: 1)
166+
- `per_page` - Results per page (default: 12)
167+
- `locale` - Locale code
168+
169+
Response:
170+
```json
171+
{
172+
"data": [...],
173+
"map_markers": [{"id": 1, "lat": 41.40, "lng": 2.17, "title": "...", "price": "..."}],
174+
"meta": {"total": 100, "page": 1, "per_page": 12, "total_pages": 9}
175+
}
176+
```
177+
178+
**`GET /api_public/v1/properties/:id`** - Get property by ID or slug
179+
180+
---
181+
182+
### Search Configuration
183+
184+
**`GET /api_public/v1/search/config`** - Get search filter options
185+
186+
Response:
187+
```json
188+
{
189+
"property_types": [{"key": "apartment", "label": "Apartment", "count": 15}],
190+
"price_options": {"sale": {"from": [...], "to": [...]}, "rent": {...}},
191+
"features": [{"key": "has_pool", "label": "Swimming Pool"}],
192+
"bedrooms": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
193+
"bathrooms": [0, 1, 2, 3, 4, 5, 6],
194+
"sort_options": [{"value": "price_asc", "label": "Price: Low to High"}]
195+
}
196+
```
197+
198+
---
199+
200+
### Site Configuration
201+
202+
**`GET /api_public/v1/site_details`** - Get website configuration
203+
204+
Response includes: `company_display_name`, `theme_name`, `contact_info`, `social_links`, `top_nav_links`, `footer_links`, `agency`
205+
206+
**`GET /api_public/v1/theme`** - Get theme/CSS configuration
207+
208+
Response: `theme_name`, `colors`, `css_variables`, `fonts`, `dark_mode`
209+
210+
---
211+
212+
### Pages & Content
213+
214+
**`GET /api_public/v1/pages/by_slug/:slug`** - Get page content by slug
215+
216+
**`GET /api_public/v1/translations?locale=xx`** - Get all translations for a locale
217+
218+
**`GET /api_public/v1/links?position=top_nav`** - Get navigation links
219+
220+
---
221+
222+
### Forms
223+
224+
**`POST /api_public/v1/enquiries`** - Submit property enquiry
225+
```json
226+
{"enquiry": {"name": "...", "email": "...", "phone": "...", "message": "...", "property_id": "..."}}
227+
```
228+
229+
**`POST /api_public/v1/contact`** - Submit general contact form
230+
```json
231+
{"contact": {"name": "...", "email": "...", "phone": "...", "subject": "...", "message": "..."}}
232+
```
233+
234+
---
235+
236+
### Dynamic Content
237+
238+
**`GET /api_public/v1/testimonials`** - Get testimonials
239+
240+
**`GET /api_public/v1/select_values?field_names=property-types`** - Get select options
241+
242+
---
243+
148244
## GraphQL API
149245

150246
The GraphQL API provides a more flexible and powerful way to query the application's data.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe "ApiPublic::V1::Contact", type: :request do
6+
let!(:website) { FactoryBot.create(:pwb_website, subdomain: 'contact-test') }
7+
let!(:agency) { website.agency || website.create_agency!(display_name: 'Test Agency', email_primary: 'test@example.com') }
8+
9+
before do
10+
host! 'contact-test.example.com'
11+
end
12+
13+
describe "POST /api_public/v1/contact" do
14+
let(:valid_params) do
15+
{
16+
contact: {
17+
name: "John Doe",
18+
email: "john@example.com",
19+
phone: "+1234567890",
20+
subject: "General Inquiry",
21+
message: "I would like more information about your services."
22+
}
23+
}
24+
end
25+
26+
it "creates a contact and message with valid params" do
27+
expect {
28+
post "/api_public/v1/contact", params: valid_params, as: :json
29+
}.to change(Pwb::Contact, :count).by(1)
30+
.and change(Pwb::Message, :count).by(1)
31+
32+
expect(response).to have_http_status(201)
33+
json = response.parsed_body
34+
expect(json["success"]).to be true
35+
expect(json["data"]["contact_id"]).to be_present
36+
expect(json["data"]["message_id"]).to be_present
37+
end
38+
39+
it "returns success message" do
40+
post "/api_public/v1/contact", params: valid_params, as: :json
41+
json = response.parsed_body
42+
expect(json["message"]).to be_present
43+
end
44+
45+
it "reuses existing contact by email" do
46+
existing_contact = website.contacts.create!(
47+
primary_email: "john@example.com",
48+
first_name: "Existing"
49+
)
50+
51+
expect {
52+
post "/api_public/v1/contact", params: valid_params, as: :json
53+
}.to change(Pwb::Contact, :count).by(0)
54+
.and change(Pwb::Message, :count).by(1)
55+
56+
expect(response).to have_http_status(201)
57+
end
58+
59+
it "returns error for missing email" do
60+
invalid_params = {
61+
contact: {
62+
name: "John Doe",
63+
message: "Hello"
64+
}
65+
}
66+
67+
post "/api_public/v1/contact", params: invalid_params, as: :json
68+
# Should either return 422 or handle missing parameter
69+
expect(response.status).to be_in([400, 422, 500])
70+
end
71+
72+
it "respects locale parameter" do
73+
post "/api_public/v1/contact", params: valid_params.merge(locale: "es"), as: :json
74+
expect(response).to have_http_status(201)
75+
end
76+
end
77+
end

spec/requests/api_public/v1/properties_spec.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,52 @@
4242
get "/api_public/v1/properties", params: { sale_or_rental: "sale" }
4343
expect(response).to have_http_status(200)
4444
end
45+
46+
it "returns data in correct structure with meta" do
47+
host! 'properties-test.example.com'
48+
get "/api_public/v1/properties", params: { sale_or_rental: "sale" }
49+
json = response.parsed_body
50+
expect(json).to have_key("data")
51+
expect(json).to have_key("meta")
52+
expect(json["meta"]).to have_key("total")
53+
expect(json["meta"]).to have_key("page")
54+
expect(json["meta"]).to have_key("per_page")
55+
expect(json["meta"]).to have_key("total_pages")
56+
end
57+
58+
it "returns map_markers array" do
59+
host! 'properties-test.example.com'
60+
get "/api_public/v1/properties", params: { sale_or_rental: "sale" }
61+
json = response.parsed_body
62+
expect(json).to have_key("map_markers")
63+
expect(json["map_markers"]).to be_an(Array)
64+
end
65+
66+
it "supports pagination parameters" do
67+
host! 'properties-test.example.com'
68+
get "/api_public/v1/properties", params: { sale_or_rental: "sale", page: 1, per_page: 5 }
69+
expect(response).to have_http_status(200)
70+
json = response.parsed_body
71+
expect(json["meta"]["page"]).to eq(1)
72+
expect(json["meta"]["per_page"]).to eq(5)
73+
end
74+
75+
it "supports highlighted filter" do
76+
host! 'properties-test.example.com'
77+
get "/api_public/v1/properties", params: { sale_or_rental: "sale", highlighted: "true" }
78+
expect(response).to have_http_status(200)
79+
end
80+
81+
it "supports limit parameter" do
82+
host! 'properties-test.example.com'
83+
get "/api_public/v1/properties", params: { sale_or_rental: "sale", limit: 3 }
84+
expect(response).to have_http_status(200)
85+
end
86+
87+
it "respects locale parameter" do
88+
host! 'properties-test.example.com'
89+
get "/api_public/v1/properties", params: { sale_or_rental: "sale", locale: "es" }
90+
expect(response).to have_http_status(200)
91+
end
4592
end
4693
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe "ApiPublic::V1::SearchConfig", type: :request do
6+
let!(:website) { FactoryBot.create(:pwb_website, subdomain: 'search-config-test') }
7+
let!(:realty_asset) { FactoryBot.create(:pwb_realty_asset, website: website, prop_type_key: 'apartment') }
8+
let!(:sale_listing) { FactoryBot.create(:pwb_sale_listing, :visible, realty_asset: realty_asset) }
9+
10+
before do
11+
host! 'search-config-test.example.com'
12+
# Refresh the materialized view so properties are visible
13+
Pwb::ListedProperty.refresh(concurrently: false)
14+
end
15+
16+
describe "GET /api_public/v1/search/config" do
17+
it "returns successful response" do
18+
get "/api_public/v1/search/config"
19+
expect(response).to have_http_status(200)
20+
end
21+
22+
it "returns property_types array" do
23+
get "/api_public/v1/search/config"
24+
json = response.parsed_body
25+
expect(json).to have_key("property_types")
26+
expect(json["property_types"]).to be_an(Array)
27+
end
28+
29+
it "returns price_options with sale and rent" do
30+
get "/api_public/v1/search/config"
31+
json = response.parsed_body
32+
expect(json).to have_key("price_options")
33+
expect(json["price_options"]).to have_key("sale")
34+
expect(json["price_options"]).to have_key("rent")
35+
end
36+
37+
it "returns bedrooms and bathrooms arrays" do
38+
get "/api_public/v1/search/config"
39+
json = response.parsed_body
40+
expect(json["bedrooms"]).to be_an(Array)
41+
expect(json["bathrooms"]).to be_an(Array)
42+
end
43+
44+
it "returns sort_options" do
45+
get "/api_public/v1/search/config"
46+
json = response.parsed_body
47+
expect(json["sort_options"]).to be_an(Array)
48+
expect(json["sort_options"].first).to have_key("value")
49+
expect(json["sort_options"].first).to have_key("label")
50+
end
51+
52+
it "respects locale parameter" do
53+
get "/api_public/v1/search/config", params: { locale: "es" }
54+
expect(response).to have_http_status(200)
55+
end
56+
end
57+
end

0 commit comments

Comments
 (0)