diff --git a/npm_package ltsdk/package.json b/npm_package ltsdk/package.json index b870e562..bfc71462 100644 --- a/npm_package ltsdk/package.json +++ b/npm_package ltsdk/package.json @@ -66,6 +66,9 @@ "ajv-formats": "^2.1.1", "axios": "^1.7.7", "dotenv": "^17.0.1", + "googleapis": "^131.0.0", + "cors": "^2.8.5", + "express": "^4.18.2" "minimist": "^1.2.8" } } diff --git a/npm_package ltsdk/src/README-googleClassroom.md b/npm_package ltsdk/src/README-googleClassroom.md new file mode 100644 index 00000000..db601a8a --- /dev/null +++ b/npm_package ltsdk/src/README-googleClassroom.md @@ -0,0 +1,106 @@ +# Google Classroom API Server (Node.js) + +This project is a small Node.js server that wraps the *Google Classroom API* and exposes endpoints to authenticate with Google, list and manage courses, coursework, students/teachers, and student submissions. It was developed as part of an LFX mentorship task. + +--- + +## Environment variables + +Create a `.env` file (or copy `.env.example`) and fill these values: + +``` +# Example .env.example +GOOGLE_CLIENT_ID=your-google-client-id +GOOGLE_CLIENT_SECRET=your-google-client-secret +GOOGLE_REDIRECT_URI=http://localhost:3000/api/google/callback +PORT=3000 +ALLOWED_ORIGIN=http://localhost:5173 # optional: restrict CORS in production +``` + +*Important:* Do not commit your real `.env` to the repo. + +--- + +## Install & run + +1. Install dependencies: + +```sh +npm install +``` + +2. Start the server: + +```sh +node googleClassroom.js +``` + +3. (Optional) Development with auto-restart (if you add `nodemon`): + +```sh +npx nodemon googleClassroom.js +``` + +After starting, open `http://localhost:3000` to see the root documentation JSON. + +--- + +## Authentication quick start + +1. Visit `GET /api/auth` — this returns an `authUrl` to visit for Google OAuth consent (or prints it to console depending on your server setup). +2. Complete the Google OAuth flow — Google will redirect to the `REDIRECT_URI` (e.g., `/api/google/callback`) with a `code`. +3. The server exchanges the code for tokens and uses them for API calls. + +--- + +## Endpoints (implemented) + +*Authentication* + +* `GET /api/auth` — Get Google OAuth URL to authorize +* `GET /api/google/callback` — OAuth2 redirect callback + +*Status & profile* + +* `GET /` — Root API info / small documentation +* `GET /api/status` — Check auth status +* `GET /api/profile` — Get authenticated user profile + +*Courses* + +* `GET /api/courses` — List courses +* `GET /api/courses/:courseId` — Get course details +* `POST /api/courses` — Create a course (`{ name, section?, description?, room? }`) + +*Users* + +* `GET /api/courses/:courseId/teachers` — List teachers +* `GET /api/courses/:courseId/students` — List students + +*Coursework* + +* `GET /api/courses/:courseId/courseWork` — List coursework +* `POST /api/courses/:courseId/courseWork` — Create coursework/assignment (`{ title, description?, maxPoints?, dueDate? }`) + +*Submissions* + +* `GET /api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions` — List submissions +* `PATCH /api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions/:submissionId` — Update/grade a submission (`{ assignedGrade?, draftGrade? }`) + +--- + +## Example: create a course (request body) + +```json +POST /api/courses +Content-Type: application/json + +{ + "name": "Intro to AI", + "section": "G", + "description": "Fall 2025 - Introductory course", + "room": "Online" +} +``` + +--- diff --git a/npm_package ltsdk/src/googleClassroom.js b/npm_package ltsdk/src/googleClassroom.js new file mode 100644 index 00000000..4c2080ff --- /dev/null +++ b/npm_package ltsdk/src/googleClassroom.js @@ -0,0 +1,466 @@ +require('dotenv').config(); +const express = require('express'); +const cors = require('cors'); +const { google } = require('googleapis'); + +const app = express(); +const PORT = process.env.PORT || 3000; + +// Middleware +app.use(cors()); +app.use(express.json()); + +// OAuth2 Setup +const oauth2Client = new google.auth.OAuth2( + process.env.GOOGLE_CLIENT_ID, + process.env.GOOGLE_CLIENT_SECRET, + process.env.GOOGLE_REDIRECT_URI +); + +// Scopes for Google Classroom API +const SCOPES = [ + 'https://www.googleapis.com/auth/classroom.courses', + 'https://www.googleapis.com/auth/classroom.coursework.me', + 'https://www.googleapis.com/auth/classroom.coursework.students', + 'https://www.googleapis.com/auth/classroom.rosters', + 'https://www.googleapis.com/auth/classroom.profile.emails', + 'https://www.googleapis.com/auth/classroom.student-submissions.me.readonly', + 'https://www.googleapis.com/auth/classroom.student-submissions.students.readonly' +]; + +// Initialize Classroom API +let classroom = null; +let tokens = null; + +// Authentication Routes +app.get('/api/auth', (req, res) => { + const authUrl = oauth2Client.generateAuthUrl({ + access_type: 'offline', + scope: SCOPES, + }); + + console.log('\n=== AUTHENTICATION REQUIRED ==='); + console.log('Visit this URL to authenticate:'); + console.log(authUrl); + console.log('================================\n'); + + res.json({ + authUrl, + message: 'Visit the authUrl to complete authentication' + }); +}); + +app.get('/api/google/callback', async (req, res) => { + try { + const { code } = req.query; + const { tokens: receivedTokens } = await oauth2Client.getToken(code); + + oauth2Client.setCredentials(receivedTokens); + tokens = receivedTokens; + classroom = google.classroom({ version: 'v1', auth: oauth2Client }); + + console.log(' Authentication successful!'); + console.log('Access Token:', receivedTokens.access_token); + console.log('Refresh Token:', receivedTokens.refresh_token); + + res.json({ + success: true, + message: 'Authentication successful! You can now use the APIs.', + tokens: receivedTokens // 👈 add this line + }); + } catch (error) { + console.error(' Authentication failed:', error.message); + res.status(500).json({ error: 'Authentication failed', details: error.message }); + } +}); + + +// Middleware to check authentication +const requireAuth = (req, res, next) => { + if (!tokens || !classroom) { + return res.status(401).json({ + error: 'Not authenticated', + message: 'Please visit /api/auth to get authentication URL' + }); + } + next(); +}; + +// === COURSE APIs === + +// GET /api/courses - List all courses +app.get('/api/courses', requireAuth, async (req, res) => { + try { + console.log(' Fetching courses...'); + + const response = await classroom.courses.list({ + pageSize: 100, + }); + + const courses = response.data.courses || []; + + console.log(` Found ${courses.length} courses`); + courses.forEach((course, index) => { + console.log(` ${index + 1}. ${course.name} (ID: ${course.id})`); + }); + + res.json({ + success: true, + totalCount: courses.length, + courses: courses + }); + } catch (error) { + console.error(' Error fetching courses:', error.message); + res.status(500).json({ error: 'Failed to fetch courses', details: error.message }); + } +}); + +// GET /api/courses/:courseId - Get specific course details +app.get('/api/courses/:courseId', requireAuth, async (req, res) => { + try { + const { courseId } = req.params; + console.log(` Fetching course details for: ${courseId}`); + + const response = await classroom.courses.get({ + id: courseId, + }); + + console.log(` Course details retrieved: ${response.data.name}`); + + res.json({ + success: true, + course: response.data + }); + } catch (error) { + console.error(' Error fetching course details:', error.message); + res.status(500).json({ error: 'Failed to fetch course details', details: error.message }); + } +}); + +// POST /api/courses - Create a new course +app.post('/api/courses', requireAuth, async (req, res) => { + try { + const { name, section, description, room } = req.body; + + if (!name) { + return res.status(400).json({ error: 'Course name is required' }); + } + + console.log(` Creating new course: ${name}`); + + const courseData = { + name, + section: section || 'Default Section', + description: description || '', + room: room || '', + ownerId: 'me', + courseState: 'ACTIVE' + }; + + const response = await classroom.courses.create({ + requestBody: courseData, + }); + + console.log(` Course created successfully: ${response.data.name} (ID: ${response.data.id})`); + + res.json({ + success: true, + message: 'Course created successfully', + course: response.data + }); + } catch (error) { + console.error(' Error creating course:', error.message); + res.status(500).json({ error: 'Failed to create course', details: error.message }); + } +}); + +// === USER APIs === + +// GET /api/courses/:courseId/teachers - List teachers in a course +app.get('/api/courses/:courseId/teachers', requireAuth, async (req, res) => { + try { + const { courseId } = req.params; + console.log(` Fetching teachers for course: ${courseId}`); + + const response = await classroom.courses.teachers.list({ + courseId, + }); + + const teachers = response.data.teachers || []; + console.log(` Found ${teachers.length} teachers`); + + res.json({ + success: true, + count: teachers.length, + teachers: teachers + }); + } catch (error) { + console.error(' Error fetching teachers:', error.message); + res.status(500).json({ error: 'Failed to fetch teachers', details: error.message }); + } +}); + +// GET /api/courses/:courseId/students - List students in a course +app.get('/api/courses/:courseId/students', requireAuth, async (req, res) => { + try { + const { courseId } = req.params; + console.log(`👨‍🎓 Fetching students for course: ${courseId}`); + + const response = await classroom.courses.students.list({ + courseId, + }); + + const students = response.data.students || []; + console.log(` Found ${students.length} students`); + + res.json({ + success: true, + count: students.length, + students: students + }); + } catch (error) { + console.error(' Error fetching students:', error.message); + res.status(500).json({ error: 'Failed to fetch students', details: error.message }); + } +}); + +// === COURSEWORK APIs === + +// GET /api/courses/:courseId/courseWork - List all coursework in a course +app.get('/api/courses/:courseId/courseWork', requireAuth, async (req, res) => { + try { + const { courseId } = req.params; + console.log(` Fetching coursework for course: ${courseId}`); + + const response = await classroom.courses.courseWork.list({ + courseId, + }); + + const courseWork = response.data.courseWork || []; + console.log(` Found ${courseWork.length} coursework items`); + courseWork.forEach((work, index) => { + console.log(` ${index + 1}. ${work.title} (ID: ${work.id})`); + }); + + res.json({ + success: true, + count: courseWork.length, + courseWork: courseWork + }); + } catch (error) { + console.error(' Error fetching coursework:', error.message); + res.status(500).json({ error: 'Failed to fetch coursework', details: error.message }); + } +}); + +// POST /api/courses/:courseId/courseWork - Create new coursework/assignment +app.post('/api/courses/:courseId/courseWork', requireAuth, async (req, res) => { + try { + const { courseId } = req.params; + const { title, description, workType, maxPoints, dueDate } = req.body; + + if (!title) { + return res.status(400).json({ error: 'Assignment title is required' }); + } + + console.log(` Creating new assignment: ${title} in course ${courseId}`); + + const courseWorkData = { + title, + description: description || '', + workType: workType || 'ASSIGNMENT', + state: 'PUBLISHED', + maxPoints: maxPoints || 100 + }; + + // Add due date if provided + if (dueDate) { + const date = new Date(dueDate); + courseWorkData.dueDate = { + year: date.getFullYear(), + month: date.getMonth() + 1, + day: date.getDate() + }; + courseWorkData.dueTime = { + hours: 23, + minutes: 59 + }; + } + + const response = await classroom.courses.courseWork.create({ + courseId, + requestBody: courseWorkData, + }); + + console.log(` Assignment created: ${response.data.title} (ID: ${response.data.id})`); + + res.json({ + success: true, + message: 'Assignment created successfully', + courseWork: response.data + }); + } catch (error) { + console.error(' Error creating assignment:', error.message); + res.status(500).json({ error: 'Failed to create assignment', details: error.message }); + } +}); + +// === SUBMISSION APIs === + +// GET /api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions - Get student submissions +app.get('/api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions', requireAuth, async (req, res) => { + try { + const { courseId, courseWorkId } = req.params; + console.log(` Fetching submissions for coursework: ${courseWorkId}`); + + const response = await classroom.courses.courseWork.studentSubmissions.list({ + courseId, + courseWorkId, + }); + + const submissions = response.data.studentSubmissions || []; + console.log(` Found ${submissions.length} submissions`); + + res.json({ + success: true, + count: submissions.length, + submissions: submissions + }); + } catch (error) { + console.error(' Error fetching submissions:', error.message); + res.status(500).json({ error: 'Failed to fetch submissions', details: error.message }); + } +}); + +// PATCH /api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions/:id - Update/grade submission +app.patch('/api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions/:id', requireAuth, async (req, res) => { + try { + const { courseId, courseWorkId, id } = req.params; + const { assignedGrade, draftGrade } = req.body; + + console.log(` Updating submission: ${id}`); + + const updateData = {}; + if (assignedGrade !== undefined) updateData.assignedGrade = assignedGrade; + if (draftGrade !== undefined) updateData.draftGrade = draftGrade; + + if (Object.keys(updateData).length === 0) { + return res.status(400).json({ error: 'No grade data provided' }); + } + + const response = await classroom.courses.courseWork.studentSubmissions.patch({ + courseId, + courseWorkId, + id, + updateMask: Object.keys(updateData).join(','), + requestBody: updateData, + }); + + console.log(' Submission updated successfully'); + + res.json({ + success: true, + message: 'Submission updated successfully', + submission: response.data + }); + } catch (error) { + console.error(' Error updating submission:', error.message); + res.status(500).json({ error: 'Failed to update submission', details: error.message }); + } +}); + +// === UTILITY APIs === + +// GET /api/profile - Get current user profile +app.get('/api/profile', requireAuth, async (req, res) => { + try { + console.log(' Fetching user profile...'); + + const response = await classroom.userProfiles.get({ + userId: 'me', + }); + + console.log(` Profile retrieved: ${response.data.name.fullName}`); + + res.json({ + success: true, + profile: response.data + }); + } catch (error) { + console.error(' Error fetching profile:', error.message); + res.status(500).json({ error: 'Failed to fetch profile', details: error.message }); + } +}); + +// GET /api/status - Check authentication status +app.get('/api/status', (req, res) => { + const isAuthenticated = !!tokens && !!classroom; + + res.json({ + authenticated: isAuthenticated, + hasClassroomAccess: isAuthenticated, + message: isAuthenticated ? 'Ready to use APIs' : 'Authentication required' + }); +}); + +// Root endpoint with API documentation +app.get('/', (req, res) => { + res.json({ + message: 'Google Classroom API Server - LFX Mentorship Task', + version: '1.0.0', + authentication: { + status: !!tokens ? 'Authenticated' : 'Not Authenticated', + authUrl: '/api/auth' + }, + endpoints: { + authentication: { + 'GET /api/auth': 'Get authentication URL', + 'GET /api/google/callback': 'OAuth callback (used by Google)', + 'GET /api/status': 'Check authentication status', + 'GET /api/profile': 'Get current user profile' + }, + courses: { + 'GET /api/courses': 'List all courses', + 'GET /api/courses/:courseId': 'Get course details', + 'POST /api/courses': 'Create new course (requires: name, optional: section, description, room)' + }, + users: { + 'GET /api/courses/:courseId/teachers': 'List teachers in course', + 'GET /api/courses/:courseId/students': 'List students in course' + }, + coursework: { + 'GET /api/courses/:courseId/courseWork': 'List coursework in course', + 'POST /api/courses/:courseId/courseWork': 'Create assignment (requires: title, optional: description, maxPoints, dueDate)' + }, + submissions: { + 'GET /api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions': 'Get student submissions', + 'PATCH /api/courses/:courseId/courseWork/:courseWorkId/studentSubmissions/:id': 'Grade submission (requires: assignedGrade or draftGrade)' + } + } + }); +}); + +// Error handling middleware +app.use((error, req, res, next) => { + console.error(' Global error:', error); + res.status(500).json({ error: 'Internal server error', details: error.message }); +}); + +// 404 handler +app.use((req, res) => { + res.status(404).json({ error: 'Endpoint not found', availableEndpoints: 'Visit GET / for API documentation' }); +}); + +app.listen(PORT, () => { + console.log('\n Google Classroom API Server Started'); + console.log(` Server running on: http://localhost:${PORT}`); + console.log(' API Documentation: GET /'); + console.log(' Authentication: GET /api/auth'); + console.log('\n=== Quick Start ==='); + console.log('1. Visit http://localhost:3000/api/auth to get authentication URL'); + console.log('2. Complete Google OAuth flow'); + console.log('3. Start using the APIs'); + console.log('==================\n'); +}); + +module.exports = app; \ No newline at end of file diff --git a/npm_package ltsdk/tests/googleClassTest.js b/npm_package ltsdk/tests/googleClassTest.js new file mode 100644 index 00000000..1565e648 --- /dev/null +++ b/npm_package ltsdk/tests/googleClassTest.js @@ -0,0 +1,285 @@ + +const axios = require('axios'); + +const BASE_URL = 'http://localhost:3000/api'; + +// Test data +const testCourse = { + name: 'LFX API Test Course', + section: 'API Demo Section', + description: 'Test course created for LFX Mentorship Task demonstration', + room: 'Virtual Room 101' +}; + +const testAssignment = { + title: 'LFX Test Assignment', + description: 'Sample assignment created via API for testing purposes', + maxPoints: 100, + workType: 'ASSIGNMENT' +}; + +class ClassroomAPITester { + constructor() { + this.courseId = null; + this.assignmentId = null; + } + + async checkStatus() { + try { + console.log('\n Checking authentication status...'); + const response = await axios.get(`${BASE_URL}/status`); + console.log('Status:', response.data); + return response.data.authenticated; + } catch (error) { + console.error(' Error checking status:', error.response?.data || error.message); + return false; + } + } + + async getProfile() { + try { + console.log('\n Getting user profile...'); + const response = await axios.get(`${BASE_URL}/profile`); + console.log(' Profile:', response.data.profile.name.fullName); + console.log('Email:', response.data.profile.emailAddress); + return response.data; + } catch (error) { + console.error(' Error getting profile:', error.response?.data || error.message); + return null; + } + } + + async listCourses() { + try { + console.log('\n Listing all courses...'); + const response = await axios.get(`${BASE_URL}/courses`); + console.log(` Found ${response.data.totalCount} courses`); + + if (response.data.courses.length > 0) { + console.log('Courses:'); + response.data.courses.forEach((course, index) => { + console.log(` ${index + 1}. ${course.name} (ID: ${course.id})`); + }); + + // Store first course ID for later tests + this.courseId = response.data.courses[0].id; + } + + return response.data; + } catch (error) { + console.error(' Error listing courses:', error.response?.data || error.message); + return null; + } + } + + async createCourse() { + try { + console.log('\n Creating test course...'); + const response = await axios.post(`${BASE_URL}/courses`, testCourse); + console.log(' Course created successfully!'); + console.log('Course ID:', response.data.course.id); + console.log('Course Name:', response.data.course.name); + + this.courseId = response.data.course.id; + return response.data; + } catch (error) { + console.error(' Error creating course:', error.response?.data || error.message); + return null; + } + } + + async getCourseDetails(courseId) { + try { + console.log(`\n Getting course details for: ${courseId}`); + const response = await axios.get(`${BASE_URL}/courses/${courseId}`); + console.log(' Course details retrieved'); + console.log('Name:', response.data.course.name); + console.log('Section:', response.data.course.section); + console.log('Description:', response.data.course.description); + return response.data; + } catch (error) { + console.error(' Error getting course details:', error.response?.data || error.message); + return null; + } + } + + async getTeachers(courseId) { + try { + console.log(`\n Getting teachers for course: ${courseId}`); + const response = await axios.get(`${BASE_URL}/courses/${courseId}/teachers`); + console.log(` Found ${response.data.count} teachers`); + + if (response.data.teachers.length > 0) { + response.data.teachers.forEach((teacher, index) => { + console.log(` ${index + 1}. ${teacher.profile.name.fullName} (${teacher.profile.emailAddress})`); + }); + } + + return response.data; + } catch (error) { + console.error(' Error getting teachers:', error.response?.data || error.message); + return null; + } + } + + async getStudents(courseId) { + try { + console.log(`\n Getting students for course: ${courseId}`); + const response = await axios.get(`${BASE_URL}/courses/${courseId}/students`); + console.log(` Found ${response.data.count} students`); + + if (response.data.students.length > 0) { + response.data.students.forEach((student, index) => { + console.log(` ${index + 1}. ${student.profile.name.fullName} (${student.profile.emailAddress})`); + }); + } + + return response.data; + } catch (error) { + console.error(' Error getting students:', error.response?.data || error.message); + return null; + } + } + + async listCoursework(courseId) { + try { + console.log(`\n Listing coursework for course: ${courseId}`); + const response = await axios.get(`${BASE_URL}/courses/${courseId}/courseWork`); + console.log(` Found ${response.data.count} coursework items`); + + if (response.data.courseWork.length > 0) { + console.log('Coursework:'); + response.data.courseWork.forEach((work, index) => { + console.log(` ${index + 1}. ${work.title} (ID: ${work.id}) - ${work.maxPoints} points`); + }); + + // Store first assignment ID for later tests + this.assignmentId = response.data.courseWork[0].id; + } + + return response.data; + } catch (error) { + console.error(' Error listing coursework:', error.response?.data || error.message); + return null; + } + } + + async createAssignment(courseId) { + try { + console.log(`\n➕ Creating test assignment in course: ${courseId}`); + const response = await axios.post(`${BASE_URL}/courses/${courseId}/courseWork`, testAssignment); + console.log(' Assignment created successfully!'); + console.log('Assignment ID:', response.data.courseWork.id); + console.log('Assignment Title:', response.data.courseWork.title); + console.log('Max Points:', response.data.courseWork.maxPoints); + + this.assignmentId = response.data.courseWork.id; + return response.data; + } catch (error) { + console.error(' Error creating assignment:', error.response?.data || error.message); + return null; + } + } + + async getSubmissions(courseId, assignmentId) { + try { + console.log(`\n Getting submissions for assignment: ${assignmentId}`); + const response = await axios.get(`${BASE_URL}/courses/${courseId}/courseWork/${assignmentId}/studentSubmissions`); + console.log(` Found ${response.data.count} submissions`); + + if (response.data.submissions.length > 0) { + response.data.submissions.forEach((submission, index) => { + const studentId = submission.userId || 'Unknown'; + const state = submission.state || 'Unknown'; + const grade = submission.assignedGrade !== undefined ? submission.assignedGrade : 'Not graded'; + console.log(` ${index + 1}. Student: ${studentId}, State: ${state}, Grade: ${grade}`); + }); + } + + return response.data; + } catch (error) { + console.error(' Error getting submissions:', error.response?.data || error.message); + return null; + } + } + + async runFullTest() { + console.log(' Starting Google Classroom API Test Suite'); + console.log('=' .repeat(50)); + + // Check authentication + const isAuthenticated = await this.checkStatus(); + if (!isAuthenticated) { + console.log('\n Not authenticated. Please run:'); + console.log('1. Start the server: npm start'); + console.log('2. Visit: http://localhost:3000/api/auth'); + console.log('3. Complete Google OAuth flow'); + console.log('4. Run this test again'); + return; + } + + console.log(' Authenticated successfully!'); + + // Test sequence + await this.getProfile(); + await this.listCourses(); + + // If no courses exist, create one + if (!this.courseId) { + await this.createCourse(); + } + + if (this.courseId) { + await this.getCourseDetails(this.courseId); + await this.getTeachers(this.courseId); + await this.getStudents(this.courseId); + await this.listCoursework(this.courseId); + + // Create assignment if none exist + if (!this.assignmentId) { + await this.createAssignment(this.courseId); + } + + if (this.assignmentId) { + await this.getSubmissions(this.courseId, this.assignmentId); + } + } + + console.log('\n API Test Suite Completed!'); + console.log('=' .repeat(50)); + } + + async runQuickTest() { + console.log(' Quick API Test'); + console.log('=' .repeat(30)); + + const isAuthenticated = await this.checkStatus(); + if (!isAuthenticated) { + console.log('\n Authentication required. Visit: http://localhost:3000/api/auth'); + return; + } + + await this.getProfile(); + await this.listCourses(); + + console.log('\n Quick test completed!'); + } +} + +// Command line usage +const args = process.argv.slice(2); +const tester = new ClassroomAPITester(); + +if (args.includes('--quick')) { + tester.runQuickTest(); +} else if (args.includes('--help')) { + console.log('Google Classroom API Tester'); + console.log('Usage:'); + console.log(' node test.js - Run full test suite'); + console.log(' node test.js --quick - Run quick test'); + console.log(' node test.js --help - Show this help'); +} else { + tester.runFullTest(); +} + +module.exports = ClassroomAPITester; \ No newline at end of file