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
2 changes: 0 additions & 2 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ export class AuthService {
role: user.role,
};
} catch (error) {
console.error('Auth error:', error);

if (error instanceof UnauthorizedException) {
throw error;
}
Expand Down
55 changes: 55 additions & 0 deletions backend/src/field-fetch-api/convertXY.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { convertXYService } from './convertXY.service';
import { InternalServerErrorException } from '@nestjs/common';

describe('convertXYService', () => {
let service: convertXYService;

beforeEach(() => {
service = new convertXYService();
});

describe('convertXYtoLatLon', () => {
it('should return [lat, lon] for valid ITM coordinates', () => {
// Example ITM coordinates of Jerusalem
const x = 219529;
const y = 626907;

const [lat, lon] = service.convertXYtoLatLon(x, y);

// Check they are numbers and fall within reasonable range for Israel
expect(typeof lat).toBe('number');
expect(typeof lon).toBe('number');
expect(lat).toBeGreaterThan(29); // South of Israel ~29°
expect(lat).toBeLessThan(34); // North of Israel ~33.5°
expect(lon).toBeGreaterThan(33); // West ~33°
expect(lon).toBeLessThan(36); // East ~35.5°
});

it('should throw InternalServerErrorException if x or y is NaN', () => {
expect(() => service.convertXYtoLatLon(NaN, 123456)).toThrow(
InternalServerErrorException,
);
expect(() => service.convertXYtoLatLon(123456, NaN)).toThrow(
InternalServerErrorException,
);
});

it('should throw InternalServerErrorException if x or y is undefined', () => {
expect(() => service.convertXYtoLatLon(undefined as any, 123456)).toThrow(
InternalServerErrorException,
);
expect(() => service.convertXYtoLatLon(123456, undefined as any)).toThrow(
InternalServerErrorException,
);
});

it('should throw InternalServerErrorException if x or y is null', () => {
expect(() => service.convertXYtoLatLon(null as any, 123456)).toThrow(
InternalServerErrorException,
);
expect(() => service.convertXYtoLatLon(123456, null as any)).toThrow(
InternalServerErrorException,
);
});
});
});
2 changes: 1 addition & 1 deletion backend/src/field-fetch-api/convertXY.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class convertXYService {
'+proj=tmerc +lat_0=31.73439361111111 +lon_0=35.20451694444445 +k=1.0000067 +x_0=219529.584 +y_0=626907.39 +ellps=GRS80 +units=m +no_defs';

convertXYtoLatLon(x: number, y: number): [number, number] {
if (!isNaN(x) && !isNaN(y)) {
if (!isNaN(x) && !isNaN(y) && x != null && y != null) {
const [lon, lat] = proj4(this.ITM, this.WGS84, [x, y]);
return [lat, lon];
} else throw new InternalServerErrorException(`${x}, ${y} not valid`);
Expand Down
305 changes: 305 additions & 0 deletions backend/src/field-fetch-api/field-fetch-api.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,305 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FieldFetchApiService } from './field-fetch-api.service';
import { HttpService } from '@nestjs/axios';
import { convertXYService } from './convertXY.service';
import { FieldsService } from 'src/fields/fields.service';
import { of } from 'rxjs';
import { GameType } from 'src/enums/game-type.enum';
import { AxiosResponse } from 'axios';

describe('FieldFetchApiService', () => {
let service: FieldFetchApiService;
let httpService: HttpService;
let fieldsService: FieldsService;
let xyService: convertXYService;

const mockRecords = [
{
_id: 1,
'רשות מקומית': 'אבו גוש',
ישוב: '',
'מספר זיהוי': '031G2308773',
'סוג מתקן': "אולם ספורט קטן – 15x24 מ'",
'שם המתקן': 'אולם התעמלות ביה"ס השלום',
'שכונה-רובע': 'עבדאלרחמן',
רחוב: 'עבדאלרחמן - התמר',
'מספר בית': '3',
'ציר X': 210525,
'ציר Y': 634529,
'מספר המבנים': null,
'בעלי המתקן': 'אבו גוש',
'גוף מפעיל המתקן':
'מועצה מקומית אבו גוש + ביה"ס יסודי השלום + מתנ"ס אבו גוש',
'טלפון איש קשר': '025700342',
'דואל איש קשר': 'mohamad.jaber81@gmail.com',
'מספר מושבים': null,
'פנוי לפעילות': 'בתיאום בלבד',
'גידור קיים': 'כן',
'תאורה קיימת': 'כן',
'נגישות לנכים': 'כן',
'חניה לרכבים': 'כן',
'מצב המתקן': 'תקין ופעיל',
'מתקן תקני לתחרויות': 'תקני וכולל שירותים, מקלחות וחדר שופטים',
'שימוש לתחרויות רשמיות': 'ליגה מקומית ואיזורית',
'שנת הקמה': 2019,
'משרת בית ספר': 'כן',
},
{
_id: 2,
'רשות מקומית': 'אבו גוש',
ישוב: '',
'מספר זיהוי': '031G3301122',
'סוג מתקן': "אולם ספורט בינוני – 32x19 מ'",
'שם המתקן': 'אולם פיס אבו גוש',
'שכונה-רובע': 'ליד תיכון אבו גוש',
רחוב: 'השלום',
'מספר בית': '12',
'ציר X': 210265,
'ציר Y': 635399,
'מספר המבנים': null,
'בעלי המתקן': 'אבו גוש',
'גוף מפעיל המתקן': 'רשות מקומית + מרכז קהילתי אבו גוש',
'טלפון איש קשר': '02-5700342',
'דואל איש קשר': 'mohamad.jaber81@gmail.com',
'מספר מושבים': 450,
'פנוי לפעילות': 'מבוקר ועד לילה',
'גידור קיים': 'כן',
'תאורה קיימת': 'כן',
'נגישות לנכים': 'כן',
'חניה לרכבים': 'כן',
'מצב המתקן': 'תקין ופעיל',
'מתקן תקני לתחרויות': 'תקני וכולל שירותים, מקלחות וחדר שופטים',
'שימוש לתחרויות רשמיות': 'מתקיימות במתקן תחרויות רשמיות',
'שנת הקמה': 2008,
'משרת בית ספר': 'חטיבת ביניים ותיכון אבו גוש',
},
{
_id: 3,
'רשות מקומית': 'אבו גוש',
ישוב: '',
'מספר זיהוי': '031B1305253',
'סוג מתקן': "מגרש כדורסל – 19X32 מ'",
'שם המתקן': 'מגרש משולב כדורגל תיכון',
'שכונה-רובע': 'ליד אולם ספורט',
רחוב: 'השלום',
'מספר בית': '12',
'ציר X': 210232,
'ציר Y': 635383,
'מספר המבנים': null,
'בעלי המתקן': 'אבו גוש',
'גוף מפעיל המתקן': 'רשות מקומית + מרכז קהילתי אבו גוש',
'טלפון איש קשר': '02-5700342',
'דואל איש קשר': 'mohamad.jaber81@gmail.com',
'מספר מושבים': 100,
'פנוי לפעילות': 'פתוח ללא הגבלה',
'גידור קיים': 'כן',
'תאורה קיימת': 'כן',
'נגישות לנכים': 'כן',
'חניה לרכבים': 'כן',
'מצב המתקן': 'פעיל וזקוק לשיפוץ',
'מתקן תקני לתחרויות': 'המתקן אינו עומד במידות התקניות לתחרויות',
'שימוש לתחרויות רשמיות': 'אינו משמש לתחרויות רשמיות',
'שנת הקמה': 1994,
'משרת בית ספר': 'חטיבת ביניים ותיכון אבו גוש',
},
{
_id: 4,
'רשות מקומית': 'אבו גוש',
ישוב: '',
'מספר זיהוי': '031B1205740',
'סוג מתקן': "מגרש כדורסל – 19X32 מ'",
'שם המתקן': 'מגרש קטן חטיבת ביניים',
'שכונה-רובע': '',
רחוב: 'השלום',
'מספר בית': '',
'ציר X': 210403,
'ציר Y': 635436,
'מספר המבנים': null,
'בעלי המתקן': 'אבו גוש',
'גוף מפעיל המתקן': 'רשות מקומית',
'טלפון איש קשר': '02-5346975',
'דואל איש קשר': 'mohamad.jaber81@gmail.com',
'מספר מושבים': 0,
'פנוי לפעילות': 'בוקר בלבד',
'גידור קיים': 'לא',
'תאורה קיימת': 'לא',
'נגישות לנכים': 'כן',
'חניה לרכבים': 'כן',
'מצב המתקן': 'פעיל וזקוק לשיפוץ',
'מתקן תקני לתחרויות': 'המתקן אינו עומד במידות התקניות לתחרויות',
'שימוש לתחרויות רשמיות': 'אינו משמש לתחרויות רשמיות',
'שנת הקמה': 2005,
'משרת בית ספר': 'חטיבת ביניים',
},
{
_id: 5,
'רשות מקומית': 'אבו גוש',
ישוב: '',
'מספר זיהוי': '031B2305241',
'סוג מתקן': "מגרש ספורט משולב – 43X32 מ'",
'שם המתקן': 'מגרש משולב יסודי מרכז',
'שכונה-רובע': 'מרכז',
רחוב: 'מחמוד רשיד',
'מספר בית': '1',
'ציר X': 210393,
'ציר Y': 635129,
'מספר המבנים': null,
'בעלי המתקן': 'אבו גוש',
'גוף מפעיל המתקן':
'רשות מקומית + מרכז קהילתי אבו גוש +ביה"ס יסודי אבו גוש מרכז',
'טלפון איש קשר': '02-5342875',
'דואל איש קשר': 'mohamad.jaber81@gmail.com',
'מספר מושבים': 150,
'פנוי לפעילות': 'פתוח ללא הגבלה',
'גידור קיים': 'כן',
'תאורה קיימת': 'כן',
'נגישות לנכים': 'כן',
'חניה לרכבים': 'כן',
'מצב המתקן': 'פעיל וזקוק לשיפוץ',
'מתקן תקני לתחרויות': 'המתקן אינו עומד במידות התקניות לתחרויות',
'שימוש לתחרויות רשמיות': 'אינו משמש לתחרויות רשמיות',
'שנת הקמה': 1975,
'משרת בית ספר': 'יסודי אבו גוש מרכז',
},
{
_id: 6,
'רשות מקומית': 'אבו גוש',
ישוב: '',
'מספר זיהוי': '031B2205252',
'סוג מתקן': "מגרש ספורט משולב – 43X32 מ'",
'שם המתקן': 'מגרש משולב כדורגל יסודי השלום',
'שכונה-רובע': 'עבדאלרחמן',
רחוב: 'עבדאלרחמן - התמר',
'מספר בית': '1',
'ציר X': 210523,
'ציר Y': 634529,
'מספר המבנים': null,
'בעלי המתקן': 'אבו גוש',
'גוף מפעיל המתקן':
'מועצה מקומית אבו גוש + ביה"ס יסודי השלום + מתנ"ס אבו גוש',
'טלפון איש קשר': '025700342',
'דואל איש קשר': 'mohamad.jaber81@gmail.com',
'מספר מושבים': 50,
'פנוי לפעילות': 'מבוקר ועד לילה',
'גידור קיים': 'כן',
'תאורה קיימת': 'לא',
'נגישות לנכים': 'כן',
'חניה לרכבים': 'כן',
'מצב המתקן': 'פעיל וזקוק לשיפוץ',
'מתקן תקני לתחרויות': 'המתקן אינו עומד במידות התקניות לתחרויות',
'שימוש לתחרויות רשמיות': 'לא רלוונטי',
'שנת הקמה': 2010,
'משרת בית ספר': 'יסודי השלום',
},
{
_id: 7,
'רשות מקומית': 'אבו סנאן',
ישוב: '',
'מספר זיהוי': '032G2100771',
'סוג מתקן': "אולם ספורט קטן – 15x24 מ'",
'שם המתקן': 'אולם ספורט',
'שכונה-רובע': 'שכונת החיילים',
רחוב: '',
'מספר בית': '',
'ציר X': 216156,
'ציר Y': 762892,
'מספר המבנים': null,
'בעלי המתקן': 'אבו סנאן',
'גוף מפעיל המתקן': 'מועצה מקומית אבו סנאן',
'טלפון איש קשר': '049964527',
'דואל איש קשר': 'tahseen.abreek@gmail.com',
'מספר מושבים': null,
'פנוי לפעילות': 'בתיאום בלבד',
'גידור קיים': 'כן',
'תאורה קיימת': 'כן',
'נגישות לנכים': 'כן',
'חניה לרכבים': 'כן',
'מצב המתקן': 'לא פעיל',
'מתקן תקני לתחרויות': 'המתקן אינו עומד במידות התקניות לתחרויות',
'שימוש לתחרויות רשמיות': '',
'שנת הקמה': 2018,
'משרת בית ספר': '',
},
];

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
FieldFetchApiService,
{
provide: HttpService,
useValue: { get: jest.fn() },
},
{
provide: convertXYService,
useValue: {
convertXYtoLatLon: jest
.fn()
.mockImplementation((x, y) => [x / 10000, y / 10000]),
},
},
{
provide: FieldsService,
useValue: {
upsertMany: jest.fn(),
},
},
],
}).compile();

service = module.get<FieldFetchApiService>(FieldFetchApiService);
httpService = module.get<HttpService>(HttpService);
fieldsService = module.get<FieldsService>(FieldsService);
xyService = module.get<convertXYService>(convertXYService);
});

it('should parse and upsert fields correctly', async () => {
const axiosResponse: Partial<AxiosResponse<any>> = {
data: {
result: {
records: mockRecords,
},
},
};

jest
.spyOn(httpService, 'get')
.mockReturnValue(of(axiosResponse as AxiosResponse));

const upsertSpy = jest.spyOn(fieldsService, 'upsertMany');

await service.getFields();

expect(httpService.get).toHaveBeenCalled();
expect(xyService.convertXYtoLatLon).toHaveBeenCalled();

// Ensure only valid entries were passed to upsertMany
expect(upsertSpy).toHaveBeenCalledWith(
expect.arrayContaining([
expect.objectContaining({
gameTypes: expect.arrayContaining([
expect.stringMatching(/^(football|basketball|tennis)$/),
]),
fieldLat: expect.any(Number),
fieldLng: expect.any(Number),
city: expect.any(String),
}),
]),
);

// Ensure number of fields matches ones with matching opening hours and valid coordinates
const fields = upsertSpy.mock.calls[0][0];
expect(fields.length).toBeGreaterThan(0);

// Example field
expect(fields[0]).toEqual(
expect.objectContaining({
fieldName: expect.any(String),
fieldLat: expect.any(Number),
fieldLng: expect.any(Number),
city: expect.any(String),
gameTypes: expect.any(Array),
}),
);
});
});
Loading