Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
language: node_js
node_js:
- "8"

services:
- docker

before_install:
- docker-compose build
services: mongodb
119 changes: 97 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
# api [![Build Status](https://travis-ci.org/classmere/api.svg?branch=master)](https://travis-ci.org/classmere/api)
The API server that powers classmere + API reference sheet.

## API Reference (beta)
- [Courses](#courses)
- [Subjects](#subjects)
- [Buildings](#buildings)
- [Search](#search)

### Courses
#### Retrieve a course
**GET** `/courses/<subject_code>/<course_number>`

Returns all information about a particular course, including information
about each of its individual sections. Note: "credits" is sometimes a range,
e.g. "1-16", therefore it is returned as an integer array.
Expand All @@ -16,7 +23,6 @@ GET /courses/ANS/327
##### Response
```json
{
"_id": "5a0f90be8309aa0001cb2cc6",
"title": "ANTH 449 BIOCULTURAL PERSPECTIVES ON HUMAN REPRODUCTION",
"subjectCode": "ANTH",
"courseNumber": 449,
Expand All @@ -29,12 +35,6 @@ GET /courses/ANS/327
life-history perspective where questions related to human reproduction
and evolutionary history are examined across the lifespan from mating and
conception through elderhood and menopause. Lec/lab.",
"prereqs": [
{
"subjectCode": null,
"courseNumber": null
}
],
"updated": "2017-11-18T01:45:34.796Z",
"sections": [
{
Expand Down Expand Up @@ -71,10 +71,62 @@ GET /courses/ANS/327
}
```

#### Retrieve a list of all courses
**GET** `/courses`

Returns a list of all courses. Each entry is an object consisting of a
`title`, `subjectCode` and `courseNumber`, the latter two of which can be
used to form a request to get the full information about a course.

##### Example
```
GET /courses
```

##### Response
```json
[
{
"title": "AAE 210 INTRODUCTION TO AEROSPACE ENGINEERING",
"subjectCode": "AAE",
"courseNumber": 210
}, {
"title": "AAE 412 SPACE SYSTEMS ENGINEERING",
"subjectCode": "AAE",
"courseNumber": 412
}
]
```

### Subjects
#### Retrieve a list of all subject codes
**GET** `/subjects`

Returns a list of all subject code strings.

##### Example
```
GET /subjects
```

##### Response
```json
[
"AAE",
"ACTG",
"AEC",
"AED",
"AG",
"AGRI",
"ALS"
]
```

### Buildings
#### Retrieve a building on the OSU campus
**GET** `/buildings/<building_abbreviation>`
Returns info on the selected building

Returns info on the selected building.

>See the campus map at http://oregonstate.edu/campusmap/ for building
abbreviations and locations. Or see the campus map at the back of the
Expand All @@ -88,7 +140,6 @@ GET /buildings/KEC
##### Response
```json
{
"_id": "5a74c25b7f06d80010525648",
"abbr": "KEC",
"name": "Kelley Engineering Center",
"address": "110 SW Park Terrace",
Expand All @@ -98,9 +149,43 @@ GET /buildings/KEC
}
```

#### Retrieve a list of all OSU buildings
**GET** `/buildings`

Returns a list of all buildings on all OSU campuses, down to the sheds(!)

##### Example
```
GET /buildings
```

##### Response
```json
[
{
"abbr": "Goss",
"name": "Goss Stadium",
"address": "430 SW Langton Place",
"buildingNumber": 108,
"latitude": 44.563424,
"longitude": -123.277824
},

{
"abbr": "Mfd",
"name": "Merryfield Hall",
"address": "1600 SW Monroe Ave",
"buildingNumber": 2,
"latitude": 44.567457,
"longitude": -123.273996
}
]
```

### Search
#### Search for courses
**GET** `/search/courses/<keyword>`

Returns a list of up to 100 matching courses, not including individual
sections.

Expand All @@ -113,7 +198,6 @@ GET /search/courses/nutrition
```json
[
{
"_id": "5a0f95348309aa0001cb3201",
"title": "HORT 316 PLANT NUTRITION",
"subjectCode": "HORT",
"courseNumber": 316,
Expand All @@ -125,12 +209,6 @@ GET /search/courses/nutrition
nutrient deficiencies and toxicities and their causes and remedies,
and plant and soil analysis. Lec/lab/rec. (CSS 205 [D-] or CSS 305
[D-] or SOIL 205 [D-] )",
"prereqs": [
{
"subjectCode": null,
"courseNumber": null
}
],
"updated": "2017-11-18T02:04:36.700Z",
"sections": [
{
Expand Down Expand Up @@ -162,18 +240,16 @@ GET /search/courses/nutrition
SOIL 205 [D-] )",
"comments": "",
"textbookUrl": ""
},
{...}
}
],
"_version": 1,
"score": 0.625
},
{...}
}
]
```

#### Search for buildings
**GET** `/search/buildings/<keyword>`

Returns a list of up to 100 matching buildings.

##### Example
Expand All @@ -185,7 +261,6 @@ GET /search/buildings/Kelley
```json
[
{
"_id": "5a74c25b7f06d80010525648",
"abbr": "KEC",
"name": "Kelley Engineering Center",
"address": "110 SW Park Terrace",
Expand Down
149 changes: 149 additions & 0 deletions __tests__/app.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/* eslint-env jest */

const util = require('util')
const exec = util.promisify(require('child_process').exec)
const request = require('supertest')
const app = require('../app')

/// Clears the test database and loads sample data for testing, assuming there
/// is a MongoDB instance running at localhost or MONGO_URL.
beforeAll(async () => {
const url = process.env.MONGO_URL || 'mongodb://localhost:27017/test'
await exec(`mongorestore --uri=${url} --drop ./__tests__/dump`)

let response = await request(app).get('/search/courses/music')
while (response.status === 500) {
await milliseconds(1000)
response = await request(app).get('/search/courses/music')
}
})

describe('/', () => {
test('succeeds and returns json', async () => {
const response = await request(app).get('/')
expect(response.type).toBe('application/json')
expect(response.statusCode).toBe(200)
})

test('returns a welcome message', async () => {
const response = await request(app).get('/')
expect(response.body.message).toBe('Welcome to the classmere api.')
})
})

describe('/courses', () => {
test('succeeds and returns json', async () => {
const response = await request(app).get('/courses')
expect(response.type).toBe('application/json')
expect(response.statusCode).toBe(200)
})

test('returns an Array', async () => {
const response = await request(app).get('/courses')
expect(Array.isArray(response.body)).toBeTruthy()
expect(response.body.length).toBeGreaterThan(100)
})

test('contains the correct keys', async () => {
const response = await request(app).get('/courses')
for (const course of response.body) {
expect(Object.getOwnPropertyNames(course)).toEqual([
'title',
'subjectCode',
'courseNumber'])
}
})
})

describe('/courses/<subject_code>/<course_number>', () => {
test('succeeds and returns json', async () => {
const response = await request(app).get('/courses/CS/161')
expect(response.type).toBe('application/json')
expect(response.statusCode).toBe(200)
})

test('doesn\'t contain _id, _version, or prereqs field', async () => {
const response = await request(app).get('/courses/CS/161')
expect(response.body).not.toHaveProperty('_id')
expect(response.body).not.toHaveProperty('_version')
expect(response.body).not.toHaveProperty('prereqs')
})

test('keys are the correct type', async () => {
const response = await request(app).get('/courses/CS/161')
expect(typeof response.body.title).toBe('string')
expect(typeof response.body.subjectCode).toBe('string')
expect(typeof response.body.courseNumber).toBe('number')
expect(typeof response.body.credits).toBe('string')
expect(typeof response.body.description).toBe('string')
})

test.skip('dates are valid ISO date strings', async () => {
const response = await request(app).get('/courses/CS/161')
expect(isISOString(response.body.updated)).toBeTruthy()
for (const section of response.body.sections) {
for (const meetingTime of section.meetingTimes) {
expect(isISOString(meetingTime.startTime)).toBeTruthy()
expect(isISOString(meetingTime.endTime)).toBeTruthy()
}
}
})
})

describe('/subjects', () => {
test('succeeds and returns json', async () => {
const response = await request(app).get('/subjects')
expect(response.type).toBe('application/json')
expect(response.statusCode).toBe(200)
})

test('returns an Array', async () => {
const response = await request(app).get('/subjects')
expect(Array.isArray(response.body)).toBeTruthy()
expect(response.body.length).toBeGreaterThan(0)
})
})

describe('/search/buildings/<keyword>', () => {
test('succeeds and returns json', async () => {
const response = await request(app).get('/search/buildings/kelley')
expect(response.type).toBe('application/json')
expect(response.statusCode).toBe(200)
})

test('doesn\'t contain _id field', async () => {
const response = await request(app).get('/search/buildings/kelley')
expect(response.body).not.toHaveProperty('_id')
})
})

describe('/search/courses/<keyword>', () => {
test('succeeds and returns json', async () => {
const response = await request(app).get('/search/courses/computer')
expect(response.type).toBe('application/json')
expect(response.statusCode).toBe(200)
})

test('doesn\'t contain _id, _version, or prereqs field', async () => {
const response = await request(app).get('/search/courses/computer')
expect(response.body).not.toHaveProperty('_id')
expect(response.body).not.toHaveProperty('_version')
expect(response.body).not.toHaveProperty('prereqs')
})
})

function isISOString (dateString) {
try {
return new Date(dateString).toISOString() === dateString
} catch (error) {
return false
}
}

function milliseconds (t) {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, t)
})
}
Binary file added __tests__/dump/test/buildings.bson
Binary file not shown.
1 change: 1 addition & 0 deletions __tests__/dump/test/buildings.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"test.buildings"}],"uuid":"dec9cce919ee4699824ccc62bdf0ca3b"}
Binary file added __tests__/dump/test/courses.bson
Binary file not shown.
1 change: 1 addition & 0 deletions __tests__/dump/test/courses.metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"test.courses"}],"uuid":"d2848c7dfc4f4f938f807abe552ad502"}
Loading