ff-8d39d39ff6ef655b9851e87252b48e1af574f452R1735">1735
+ "description": "Calendar identifier.",
|
|
1736
|
+ "required": true,
|
|
1737
|
+ "location": "path"
|
|
1738
|
+ }
|
|
1739
|
+ },
|
|
1740
|
+ "parameterOrder": [
|
|
1741
|
+ "calendarId"
|
|
1742
|
+ ],
|
|
1743
|
+ "request": {
|
|
1744
|
+ "$ref": "Calendar"
|
|
1745
|
+ },
|
|
1746
|
+ "response": {
|
|
1747
|
+ "$ref": "Calendar"
|
|
1748
|
+ },
|
|
1749
|
+ "scopes": [
|
|
1750
|
+ "https://www.googleapis.com/auth/calendar"
|
|
1751
|
+ ]
|
|
1752
|
+ },
|
|
1753
|
+ "update": {
|
|
1754
|
+ "id": "calendar.calendars.update",
|
|
1755
|
+ "path": "calendars/{calendarId}",
|
|
1756
|
+ "httpMethod": "PUT",
|
|
1757
|
+ "description": "Updates metadata for a calendar.",
|
|
1758
|
+ "parameters": {
|
|
1759
|
+ "calendarId": {
|
|
1760
|
+ "type": "string",
|
|
1761
|
+ "description": "Calendar identifier.",
|
|
1762
|
+ "required": true,
|
|
1763
|
+ "location": "path"
|
|
1764
|
+ }
|
|
1765
|
+ },
|
|
1766
|
+ "parameterOrder": [
|
|
1767
|
+ "calendarId"
|
|
1768
|
+ ],
|
|
1769
|
+ "request": {
|
|
1770
|
+ "$ref": "Calendar"
|
|
1771
|
+ },
|
|
1772
|
+ "response": {
|
|
1773
|
+ "$ref": "Calendar"
|
|
1774
|
+ },
|
|
1775
|
+ "scopes": [
|
|
1776
|
+ "https://www.googleapis.com/auth/calendar"
|
|
1777
|
+ ]
|
|
1778
|
+ }
|
|
1779
|
+ }
|
|
1780
|
+ },
|
|
1781
|
+ "channels": {
|
|
1782
|
+ "methods": {
|
|
1783
|
+ "stop": {
|
|
1784
|
+ "id": "calendar.channels.stop",
|
|
1785
|
+ "path": "channels/stop",
|
|
1786
|
+ "httpMethod": "POST",
|
|
1787
|
+ "description": "Stop watching resources through this channel",
|
|
1788
|
+ "request": {
|
|
1789
|
+ "$ref": "Channel",
|
|
1790
|
+ "parameterName": "resource"
|
|
1791
|
+ },
|
|
1792
|
+ "scopes": [
|
|
1793
|
+ "https://www.googleapis.com/auth/calendar",
|
|
1794
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
1795
|
+ ]
|
|
1796
|
+ }
|
|
1797
|
+ }
|
|
1798
|
+ },
|
|
1799
|
+ "colors": {
|
|
1800
|
+ "methods": {
|
|
1801
|
+ "get": {
|
|
1802
|
+ "id": "calendar.colors.get",
|
|
1803
|
+ "path": "colors",
|
|
1804
|
+ "httpMethod": "GET",
|
|
1805
|
+ "description": "Returns the color definitions for calendars and events.",
|
|
1806
|
+ "response": {
|
|
1807
|
+ "$ref": "Colors"
|
|
1808
|
+ },
|
|
1809
|
+ "scopes": [
|
|
1810
|
+ "https://www.googleapis.com/auth/calendar",
|
|
1811
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
1812
|
+ ]
|
|
1813
|
+ }
|
|
1814
|
+ }
|
|
1815
|
+ },
|
|
1816
|
+ "events": {
|
|
1817
|
+ "methods": {
|
|
1818
|
+ "delete": {
|
|
1819
|
+ "id": "calendar.events.delete",
|
|
1820
|
+ "path": "calendars/{calendarId}/events/{eventId}",
|
|
1821
|
+ "httpMethod": "DELETE",
|
|
1822
|
+ "description": "Deletes an event.",
|
|
1823
|
+ "parameters": {
|
|
1824
|
+ "calendarId": {
|
|
1825
|
+ "type": "string",
|
|
1826
|
+ "description": "Calendar identifier.",
|
|
1827
|
+ "required": true,
|
|
1828
|
+ "location": "path"
|
|
1829
|
+ },
|
|
1830
|
+ "eventId": {
|
|
1831
|
+ "type": "string",
|
|
1832
|
+ "description": "Event identifier.",
|
|
1833
|
+ "required": true,
|
|
1834
|
+ "location": "path"
|
|
1835
|
+ },
|
|
1836
|
+ "sendNotifications": {
|
|
1837
|
+ "type": "boolean",
|
|
1838
|
+ "description": "Whether to send notifications about the deletion of the event. Optional. The default is False.",
|
|
1839
|
+ "location": "query"
|
|
1840
|
+ }
|
|
1841
|
+ },
|
|
1842
|
+ "parameterOrder": [
|
|
1843
|
+ "calendarId",
|
|
1844
|
+ "eventId"
|
|
1845
|
+ ],
|
|
1846
|
+ "scopes": [
|
|
1847
|
+ "https://www.googleapis.com/auth/calendar"
|
|
1848
|
+ ]
|
|
1849
|
+ },
|
|
1850
|
+ "get": {
|
|
1851
|
+ "id": "calendar.events.get",
|
|
1852
|
+ "path": "calendars/{calendarId}/events/{eventId}",
|
|
1853
|
+ "httpMethod": "GET",
|
|
1854
|
+ "description": "Returns an event.",
|
|
1855
|
+ "parameters": {
|
|
1856
|
+ "alwaysIncludeEmail": {
|
|
1857
|
+ "type": "boolean",
|
|
1858
|
+ "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
|
|
1859
|
+ "location": "query"
|
|
1860
|
+ },
|
|
1861
|
+ "calendarId": {
|
|
1862
|
+ "type": "string",
|
|
1863
|
+ "description": "Calendar identifier.",
|
|
1864
|
+ "required": true,
|
|
1865
|
+ "location": "path"
|
|
1866
|
+ },
|
|
1867
|
+ "eventId": {
|
|
1868
|
+ "type": "string",
|
|
1869
|
+ "description": "Event identifier.",
|
|
1870
|
+ "required": true,
|
|
1871
|
+ "location": "path"
|
|
1872
|
+ },
|
|
1873
|
+ "maxAttendees": {
|
|
1874
|
+ "type": "integer",
|
|
1875
|
+ "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
|
|
1876
|
+ "format": "int32",
|
|
1877
|
+ "minimum": "1",
|
|
1878
|
+ "location": "query"
|
|
1879
|
+ },
|
|
1880
|
+ "timeZone": {
|
|
1881
|
+ "type": "string",
|
|
1882
|
+ "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
|
|
1883
|
+ "location": "query"
|
|
1884
|
+ }
|
|
1885
|
+ },
|
|
1886
|
+ "parameterOrder": [
|
|
1887
|
+ "calendarId",
|
|
1888
|
+ "eventId"
|
|
1889
|
+ ],
|
|
1890
|
+ "response": {
|
|
1891
|
+ "$ref": "Event"
|
|
1892
|
+ },
|
|
1893
|
+ "scopes": [
|
|
1894
|
+ "https://www.googleapis.com/auth/calendar",
|
|
1895
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
1896
|
+ ]
|
|
1897
|
+ },
|
|
1898
|
+ "import": {
|
|
1899
|
+ "id": "calendar.events.import",
|
|
1900
|
+ "path": "calendars/{calendarId}/events/import",
|
|
1901
|
+ "httpMethod": "POST",
|
|
1902
|
+ "description": "Imports an event. This operation is used to add a private copy of an existing event to a calendar.",
|
|
1903
|
+ "parameters": {
|
|
1904
|
+ "calendarId": {
|
|
1905
|
+ "type": "string",
|
|
1906
|
+ "description": "Calendar identifier.",
|
|
1907
|
+ "required": true,
|
|
1908
|
+ "location": "path"
|
|
1909
|
+ }
|
|
1910
|
+ },
|
|
1911
|
+ "parameterOrder": [
|
|
1912
|
+ "calendarId"
|
|
1913
|
+ ],
|
|
1914
|
+ "request": {
|
|
1915
|
+ "$ref": "Event"
|
|
1916
|
+ },
|
|
1917
|
+ "response": {
|
|
1918
|
+ "$ref": "Event"
|
|
1919
|
+ },
|
|
1920
|
+ "scopes": [
|
|
1921
|
+ "https://www.googleapis.com/auth/calendar"
|
|
1922
|
+ ]
|
|
1923
|
+ },
|
|
1924
|
+ "insert": {
|
|
1925
|
+ "id": "calendar.events.insert",
|
|
1926
|
+ "path": "calendars/{calendarId}/events",
|
|
1927
|
+ "httpMethod": "POST",
|
|
1928
|
+ "description": "Creates an event.",
|
|
1929
|
+ "parameters": {
|
|
1930
|
+ "calendarId": {
|
|
1931
|
+ "type": "string",
|
|
1932
|
+ "description": "Calendar identifier.",
|
|
1933
|
+ "required": true,
|
|
1934
|
+ "location": "path"
|
|
1935
|
+ },
|
|
1936
|
+ "maxAttendees": {
|
|
1937
|
+ "type": "integer",
|
|
1938
|
+ "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
|
|
1939
|
+ "format": "int32",
|
|
1940
|
+ "minimum": "1",
|
|
1941
|
+ "location": "query"
|
|
1942
|
+ },
|
|
1943
|
+ "sendNotifications": {
|
|
1944
|
+ "type": "boolean",
|
|
1945
|
+ "description": "Whether to send notifications about the creation of the new event. Optional. The default is False.",
|
|
1946
|
+ "location": "query"
|
|
1947
|
+ }
|
|
1948
|
+ },
|
|
1949
|
+ "parameterOrder": [
|
|
1950
|
+ "calendarId"
|
|
1951
|
+ ],
|
|
1952
|
+ "request": {
|
|
1953
|
+ "$ref": "Event"
|
|
1954
|
+ },
|
|
1955
|
+ "response": {
|
|
1956
|
+ "$ref": "Event"
|
|
1957
|
+ },
|
|
1958
|
+ "scopes": [
|
|
1959
|
+ "https://www.googleapis.com/auth/calendar"
|
|
1960
|
+ ]
|
|
1961
|
+ },
|
|
1962
|
+ "instances": {
|
|
1963
|
+ "id": "calendar.events.instances",
|
|
1964
|
+ "path": "calendars/{calendarId}/events/{eventId}/instances",
|
|
1965
|
+ "httpMethod": "GET",
|
|
1966
|
+ "description": "Returns instances of the specified recurring event.",
|
|
1967
|
+ "parameters": {
|
|
1968
|
+ "alwaysIncludeEmail": {
|
|
1969
|
+ "type": "boolean",
|
|
1970
|
+ "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
|
|
1971
|
+ "location": "query"
|
|
1972
|
+ },
|
|
1973
|
+ "calendarId": {
|
|
1974
|
+ "type": "string",
|
|
1975
|
+ "description": "Calendar identifier.",
|
|
1976
|
+ "required": true,
|
|
1977
|
+ "location": "path"
|
|
1978
|
+ },
|
|
1979
|
+ "eventId": {
|
|
1980
|
+ "type": "string",
|
|
1981
|
+ "description": "Recurring event identifier.",
|
|
1982
|
+ "required": true,
|
|
1983
|
+ "location": "path"
|
|
1984
|
+ },
|
|
1985
|
+ "maxAttendees": {
|
|
1986
|
+ "type": "integer",
|
|
1987
|
+ "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
|
|
1988
|
+ "format": "int32",
|
|
1989
|
+ "minimum": "1",
|
|
1990
|
+ "location": "query"
|
|
1991
|
+ },
|
|
1992
|
+ "maxResults": {
|
|
1993
|
+ "type": "integer",
|
|
1994
|
+ "description": "Maximum number of events returned on one result page. By default the value is 250 events. The page size can never be larger than 2500 events. Optional.",
|
|
1995
|
+ "format": "int32",
|
|
1996
|
+ "minimum": "1",
|
|
1997
|
+ "location": "query"
|
|
1998
|
+ },
|
|
1999
|
+ "originalStart": {
|
|
2000
|
+ "type": "string",
|
|
2001
|
+ "description": "The original start time of the instance in the result. Optional.",
|
|
2002
|
+ "location": "query"
|
|
2003
|
+ },
|
|
2004
|
+ "pageToken": {
|
|
2005
|
+ "type": "string",
|
|
2006
|
+ "description": "Token specifying which result page to return. Optional.",
|
|
2007
|
+ "location": "query"
|
|
2008
|
+ },
|
|
2009
|
+ "showDeleted": {
|
|
2010
|
+ "type": "boolean",
|
|
2011
|
+ "description": "Whether to include deleted events (with status equals \"cancelled\") in the result. Cancelled instances of recurring events will still be included if singleEvents is False. Optional. The default is False.",
|
|
2012
|
+ "location": "query"
|
|
2013
|
+ },
|
|
2014
|
+ "timeMax": {
|
|
2015
|
+ "type": "string",
|
|
2016
|
+ "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time.",
|
|
2017
|
+ "format": "date-time",
|
|
2018
|
+ "location": "query"
|
|
2019
|
+ },
|
|
2020
|
+ "timeMin": {
|
|
2021
|
+ "type": "string",
|
|
2022
|
+ "description": "Lower bound (inclusive) for an event's end time to filter by. Optional. The default is not to filter by end time.",
|
|
2023
|
+ "format": "date-time",
|
|
2024
|
+ "location": "query"
|
|
2025
|
+ },
|
|
2026
|
+ "timeZone": {
|
|
2027
|
+ "type": "string",
|
|
2028
|
+ "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
|
|
2029
|
+ "location": "query"
|
|
2030
|
+ }
|
|
2031
|
+ },
|
|
2032
|
+ "parameterOrder": [
|
|
2033
|
+ "calendarId",
|
|
2034
|
+ "eventId"
|
|
2035
|
+ ],
|
|
2036
|
+ "response": {
|
|
2037
|
+ "$ref": "Events"
|
|
2038
|
+ },
|
|
2039
|
+ "scopes": [
|
|
2040
|
+ "https://www.googleapis.com/auth/calendar",
|
|
2041
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
2042
|
+ ],
|
|
2043
|
+ "supportsSubscription": true
|
|
2044
|
+ },
|
|
2045
|
+ "list": {
|
|
2046
|
+ "id": "calendar.events.list",
|
|
2047
|
+ "path": "calendars/{calendarId}/events",
|
|
2048
|
+ "httpMethod": "GET",
|
|
2049
|
+ "description": "Returns events on the specified calendar.",
|
|
2050
|
+ "parameters": {
|
|
2051
|
+ "alwaysIncludeEmail": {
|
|
2052
|
+ "type": "boolean",
|
|
2053
|
+ "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
|
|
2054
|
+ "location": "query"
|
|
2055
|
+ },
|
|
2056
|
+ "calendarId": {
|
|
2057
|
+ "type": "string",
|
|
2058
|
+ "description": "Calendar identifier.",
|
|
2059
|
+ "required": true,
|
|
2060
|
+ "location": "path"
|
|
2061
|
+ },
|
|
2062
|
+ "iCalUID": {
|
|
2063
|
+ "type": "string",
|
|
2064
|
+ "description": "Specifies event ID in the iCalendar format to be included in the response. Optional.",
|
|
2065
|
+ "location": "query"
|
|
2066
|
+ },
|
|
2067
|
+ "maxAttendees": {
|
|
2068
|
+ "type": "integer",
|
|
2069
|
+ "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
|
|
2070
|
+ "format": "int32",
|
|
2071
|
+ "minimum": "1",
|
|
2072
|
+ "location": "query"
|
|
2073
|
+ },
|
|
2074
|
+ "maxResults": {
|
|
2075
|
+ "type": "integer",
|
|
2076
|
+ "description": "Maximum number of events returned on one result page. By default the value is 250 events. The page size can never be larger than 2500 events. Optional.",
|
|
2077
|
+ "format": "int32",
|
|
2078
|
+ "minimum": "1",
|
|
2079
|
+ "location": "query"
|
|
2080
|
+ },
|
|
2081
|
+ "orderBy": {
|
|
2082
|
+ "type": "string",
|
|
2083
|
+ "description": "The order of the events returned in the result. Optional. The default is an unspecified, stable order.",
|
|
2084
|
+ "enum": [
|
|
2085
|
+ "startTime",
|
|
2086
|
+ "updated"
|
|
2087
|
+ ],
|
|
2088
|
+ "enumDescriptions": [
|
|
2089
|
+ "Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)",
|
|
2090
|
+ "Order by last modification time (ascending)."
|
|
2091
|
+ ],
|
|
2092
|
+ "location": "query"
|
|
2093
|
+ },
|
|
2094
|
+ "pageToken": {
|
|
2095
|
+ "type": "string",
|
|
2096
|
+ "description": "Token specifying which result page to return. Optional.",
|
|
2097
|
+ "location": "query"
|
|
2098
|
+ },
|
|
2099
|
+ "privateExtendedProperty": {
|
|
2100
|
+ "type": "string",
|
|
2101
|
+ "description": "Extended properties constraint specified as propertyName=value. Matches only private properties. This parameter might be repeated multiple times to return events that match all given constraints.",
|
|
2102
|
+ "repeated": true,
|
|
2103
|
+ "location": "query"
|
|
2104
|
+ },
|
|
2105
|
+ "q": {
|
|
2106
|
+ "type": "string",
|
|
2107
|
+ "description": "Free text search terms to find events that match these terms in any field, except for extended properties. Optional.",
|
|
2108
|
+ "location": "query"
|
|
2109
|
+ },
|
|
2110
|
+ "sharedExtendedProperty": {
|
|
2111
|
+ "type": "string",
|
|
2112
|
+ "description": "Extended properties constraint specified as propertyName=value. Matches only shared properties. This parameter might be repeated multiple times to return events that match all given constraints.",
|
|
2113
|
+ "repeated": true,
|
|
2114
|
+ "location": "query"
|
|
2115
|
+ },
|
|
2116
|
+ "showDeleted": {
|
|
2117
|
+ "type": "boolean",
|
|
2118
|
+ "description": "Whether to include deleted events (with status equals \"cancelled\") in the result. Cancelled instances of recurring events (but not the underlying recurring event) will still be included if showDeleted and singleEvents are both False. If showDeleted and singleEvents are both True, only single instances of deleted events (but not the underlying recurring events) are returned. Optional. The default is False.",
|
|
2119
|
+ "location": "query"
|
|
2120
|
+ },
|
|
2121
|
+ "showHiddenInvitations": {
|
|
2122
|
+ "type": "boolean",
|
|
2123
|
+ "description": "Whether to include hidden invitations in the result. Optional. The default is False.",
|
|
2124
|
+ "location": "query"
|
|
2125
|
+ },
|
|
2126
|
+ "singleEvents": {
|
|
2127
|
+ "type": "boolean",
|
|
2128
|
+ "description": "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves. Optional. The default is False.",
|
|
2129
|
+ "location": "query"
|
|
2130
|
+ },
|
|
2131
|
+ "syncToken": {
|
|
2132
|
+ "type": "string",
|
|
2133
|
+ "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. All events deleted since the previous list request will always be in the result set and it is not allowed to set showDeleted to False.\nThere are several query parameters that cannot be specified together with nextSyncToken to ensure consistency of the client state.\n\nThese are: \n- iCalUID \n- orderBy \n- privateExtendedProperty \n- q \n- sharedExtendedProperty \n- timeMin \n- timeMax \n- updatedMin If the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
|
|
2134
|
+ "location": "query"
|
|
2135
|
+ },
|
|
2136
|
+ "timeMax": {
|
|
2137
|
+ "type": "string",
|
|
2138
|
+ "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time.",
|
|
2139
|
+ "format": "date-time",
|
|
2140
|
+ "location": "query"
|
|
2141
|
+ },
|
|
2142
|
+ "timeMin": {
|
|
2143
|
+ "type": "string",
|
|
2144
|
+ "description": "Lower bound (inclusive) for an event's end time to filter by. Optional. The default is not to filter by end time.",
|
|
2145
|
+ "format": "date-time",
|
|
2146
|
+ "location": "query"
|
|
2147
|
+ },
|
|
2148
|
+ "timeZone": {
|
|
2149
|
+ "type": "string",
|
|
2150
|
+ "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
|
|
2151
|
+ "location": "query"
|
|
2152
|
+ },
|
|
2153
|
+ "updatedMin": {
|
|
2154
|
+ "type": "string",
|
|
2155
|
+ "description": "Lower bound for an event's last modification time (as a RFC 3339 timestamp) to filter by. When specified, entries deleted since this time will always be included regardless of showDeleted. Optional. The default is not to filter by last modification time.",
|
|
2156
|
+ "format": "date-time",
|
|
2157
|
+ "location": "query"
|
|
2158
|
+ }
|
|
2159
|
+ },
|
|
2160
|
+ "parameterOrder": [
|
|
2161
|
+ "calendarId"
|
|
2162
|
+ ],
|
|
2163
|
+ "response": {
|
|
2164
|
+ "$ref": "Events"
|
|
2165
|
+ },
|
|
2166
|
+ "scopes": [
|
|
2167
|
+ "https://www.googleapis.com/auth/calendar",
|
|
2168
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
2169
|
+ ],
|
|
2170
|
+ "supportsSubscription": true
|
|
2171
|
+ },
|
|
2172
|
+ "move": {
|
|
2173
|
+ "id": "calendar.events.move",
|
|
2174
|
+ "path": "calendars/{calendarId}/events/{eventId}/move",
|
|
2175
|
+ "httpMethod": "POST",
|
|
2176
|
+ "description": "Moves an event to another calendar, i.e. changes an event's organizer.",
|
|
2177
|
+ "parameters": {
|
|
2178
|
+ "calendarId": {
|
|
2179
|
+ "type": "string",
|
|
2180
|
+ "description": "Calendar identifier of the source calendar where the event currently is on.",
|
|
2181
|
+ "required": true,
|
|
2182
|
+ "location": "path"
|
|
2183
|
+ },
|
|
2184
|
+ "destination": {
|
|
2185
|
+ "type": "string",
|
|
2186
|
+ "description": "Calendar identifier of the target calendar where the event is to be moved to.",
|
|
2187
|
+ "required": true,
|
|
2188
|
+ "location": "query"
|
|
2189
|
+ },
|
|
2190
|
+ "eventId": {
|
|
2191
|
+ "type": "string",
|
|
2192
|
+ "description": "Event identifier.",
|
|
2193
|
+ "required": true,
|
|
2194
|
+ "location": "path"
|
|
2195
|
+ },
|
|
2196
|
+ "sendNotifications": {
|
|
2197
|
+ "type": "boolean",
|
|
2198
|
+ "description": "Whether to send notifications about the change of the event's organizer. Optional. The default is False.",
|
|
2199
|
+ "location": "query"
|
|
2200
|
+ }
|
|
2201
|
+ },
|
|
2202
|
+ "parameterOrder": [
|
|
2203
|
+ "calendarId",
|
|
2204
|
+ "eventId",
|
|
2205
|
+ "destination"
|
|
2206
|
+ ],
|
|
2207
|
+ "response": {
|
|
2208
|
+ "$ref": "Event"
|
|
2209
|
+ },
|
|
2210
|
+ "scopes": [
|
|
2211
|
+ "https://www.googleapis.com/auth/calendar"
|
|
2212
|
+ ]
|
|
2213
|
+ },
|
|
2214
|
+ "patch": {
|
|
2215
|
+ "id": "calendar.events.patch",
|
|
2216
|
+ "path": "calendars/{calendarId}/events/{eventId}",
|
|
2217
|
+ "httpMethod": "PATCH",
|
|
2218
|
+ "description": "Updates an event. This method supports patch semantics.",
|
|
2219
|
+ "parameters": {
|
|
2220
|
+ "alwaysIncludeEmail": {
|
|
2221
|
+ "type": "boolean",
|
|
2222
|
+ "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
|
|
2223
|
+ "location": "query"
|
|
2224
|
+ },
|
|
2225
|
+ "calendarId": {
|
|
2226
|
+ "type": "string",
|
|
2227
|
+ "description": "Calendar identifier.",
|
|
2228
|
+ "required": true,
|
|
2229
|
+ "location": "path"
|
|
2230
|
+ },
|
|
2231
|
+ "eventId": {
|
|
2232
|
+ "type": "string",
|
|
2233
|
+ "description": "Event identifier.",
|
|
2234
|
+ "required": true,
|
|
2235
|
+ "location": "path"
|
|
2236
|
+ },
|
|
2237
|
+ "maxAttendees": {
|
|
2238
|
+ "type": "integer",
|
|
2239
|
+ "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
|
|
2240
|
+ "format": "int32",
|
|
2241
|
+ "minimum": "1",
|
|
2242
|
+ "location": "query"
|
|
2243
|
+ },
|
|
2244
|
+ "sendNotifications": {
|
|
2245
|
+ "type": "boolean",
|
|
2246
|
+ "description": "Whether to send notifications about the event update (e.g. attendee's responses, title changes, etc.). Optional. The default is False.",
|
|
2247
|
+ "location": "query"
|
|
2248
|
+ }
|
|
2249
|
+ },
|
|
2250
|
+ "parameterOrder": [
|
|
2251
|
+ "calendarId",
|
|
2252
|
+ "eventId"
|
|
2253
|
+ ],
|
|
2254
|
+ "request": {
|
|
2255
|
+ "$ref": "Event"
|
|
2256
|
+ },
|
|
2257
|
+ "response": {
|
|
2258
|
+ "$ref": "Event"
|
|
2259
|
+ },
|
|
2260
|
+ "scopes": [
|
|
2261
|
+ "https://www.googleapis.com/auth/calendar"
|
|
2262
|
+ ]
|
|
2263
|
+ },
|
|
2264
|
+ "quickAdd": {
|
|
2265
|
+ "id": "calendar.events.quickAdd",
|
|
2266
|
+ "path": "calendars/{calendarId}/events/quickAdd",
|
|
2267
|
+ "httpMethod": "POST",
|
|
2268
|
+ "description": "Creates an event based on a simple text string.",
|
|
2269
|
+ "parameters": {
|
|
2270
|
+ "calendarId": {
|
|
2271
|
+ "type": "string",
|
|
2272
|
+ "description": "Calendar identifier.",
|
|
2273
|
+ "required": true,
|
|
2274
|
+ "location": "path"
|
|
2275
|
+ },
|
|
2276
|
+ "sendNotifications": {
|
|
2277
|
+ "type": "boolean",
|
|
2278
|
+ "description": "Whether to send notifications about the creation of the event. Optional. The default is False.",
|
|
2279
|
+ "location": "query"
|
|
2280
|
+ },
|
|
2281
|
+ "text": {
|
|
2282
|
+ "type": "string",
|
|
2283
|
+ "description": "The text describing the event to be created.",
|
|
2284
|
+ "required": true,
|
|
2285
|
+ "location": "query"
|
|
2286
|
+ }
|
|
2287
|
+ },
|
|
2288
|
+ "parameterOrder": [
|
|
2289
|
+ "calendarId",
|
|
2290
|
+ "text"
|
|
2291
|
+ ],
|
|
2292
|
+ "response": {
|
|
2293
|
+ "$ref": "Event"
|
|
2294
|
+ },
|
|
2295
|
+ "scopes": [
|
|
2296
|
+ "https://www.googleapis.com/auth/calendar"
|
|
2297
|
+ ]
|
|
2298
|
+ },
|
|
2299
|
+ "update": {
|
|
2300
|
+ "id": "calendar.events.update",
|
|
2301
|
+ "path": "calendars/{calendarId}/events/{eventId}",
|
|
2302
|
+ "httpMethod": "PUT",
|
|
2303
|
+ "description": "Updates an event.",
|
|
2304
|
+ "parameters": {
|
|
2305
|
+ "alwaysIncludeEmail": {
|
|
2306
|
+ "type": "boolean",
|
|
2307
|
+ "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
|
|
2308
|
+ "location": "query"
|
|
2309
|
+ },
|
|
2310
|
+ "calendarId": {
|
|
2311
|
+ "type": "string",
|
|
2312
|
+ "description": "Calendar identifier.",
|
|
2313
|
+ "required": true,
|
|
2314
|
+ "location": "path"
|
|
2315
|
+ },
|
|
2316
|
+ "eventId": {
|
|
2317
|
+ "type": "string",
|
|
2318
|
+ "description": "Event identifier.",
|
|
2319
|
+ "required": true,
|
|
2320
|
+ "location": "path"
|
|
2321
|
+ },
|
|
2322
|
+ "maxAttendees": {
|
|
2323
|
+ "type": "integer",
|
|
2324
|
+ "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
|
|
2325
|
+ "format": "int32",
|
|
2326
|
+ "minimum": "1",
|
|
2327
|
+ "location": "query"
|
|
2328
|
+ },
|
|
2329
|
+ "sendNotifications": {
|
|
2330
|
+ "type": "boolean",
|
|
2331
|
+ "description": "Whether to send notifications about the event update (e.g. attendee's responses, title changes, etc.). Optional. The default is False.",
|
|
2332
|
+ "location": "query"
|
|
2333
|
+ }
|
|
2334
|
+ },
|
|
2335
|
+ "parameterOrder": [
|
|
2336
|
+ "calendarId",
|
|
2337
|
+ "eventId"
|
|
2338
|
+ ],
|
|
2339
|
+ "request": {
|
|
2340
|
+ "$ref": "Event"
|
|
2341
|
+ },
|
|
2342
|
+ "response": {
|
|
2343
|
+ "$ref": "Event"
|
|
2344
|
+ },
|
|
2345
|
+ "scopes": [
|
|
2346
|
+ "https://www.googleapis.com/auth/calendar"
|
|
2347
|
+ ]
|
|
2348
|
+ },
|
|
2349
|
+ "watch": {
|
|
2350
|
+ "id": "calendar.events.watch",
|
|
2351
|
+ "path": "calendars/{calendarId}/events/watch",
|
|
2352
|
+ "httpMethod": "POST",
|
|
2353
|
+ "description": "Watch for changes to Events resources.",
|
|
2354
|
+ "parameters": {
|
|
2355
|
+ "alwaysIncludeEmail": {
|
|
2356
|
+ "type": "boolean",
|
|
2357
|
+ "description": "Whether to always include a value in the email field for the organizer, creator and attendees, even if no real email is available (i.e. a generated, non-working value will be provided). The use of this option is discouraged and should only be used by clients which cannot handle the absence of an email address value in the mentioned places. Optional. The default is False.",
|
|
2358
|
+ "location": "query"
|
|
2359
|
+ },
|
|
2360
|
+ "calendarId": {
|
|
2361
|
+ "type": "string",
|
|
2362
|
+ "description": "Calendar identifier.",
|
|
2363
|
+ "required": true,
|
|
2364
|
+ "location": "path"
|
|
2365
|
+ },
|
|
2366
|
+ "iCalUID": {
|
|
2367
|
+ "type": "string",
|
|
2368
|
+ "description": "Specifies event ID in the iCalendar format to be included in the response. Optional.",
|
|
2369
|
+ "location": "query"
|
|
2370
|
+ },
|
|
2371
|
+ "maxAttendees": {
|
|
2372
|
+ "type": "integer",
|
|
2373
|
+ "description": "The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned. Optional.",
|
|
2374
|
+ "format": "int32",
|
|
2375
|
+ "minimum": "1",
|
|
2376
|
+ "location": "query"
|
|
2377
|
+ },
|
|
2378
|
+ "maxResults": {
|
|
2379
|
+ "type": "integer",
|
|
2380
|
+ "description": "Maximum number of events returned on one result page. By default the value is 250 events. The page size can never be larger than 2500 events. Optional.",
|
|
2381
|
+ "format": "int32",
|
|
2382
|
+ "minimum": "1",
|
|
2383
|
+ "location": "query"
|
|
2384
|
+ },
|
|
2385
|
+ "orderBy": {
|
|
2386
|
+ "type": "string",
|
|
2387
|
+ "description": "The order of the events returned in the result. Optional. The default is an unspecified, stable order.",
|
|
2388
|
+ "enum": [
|
|
2389
|
+ "startTime",
|
|
2390
|
+ "updated"
|
|
2391
|
+ ],
|
|
2392
|
+ "enumDescriptions": [
|
|
2393
|
+ "Order by the start date/time (ascending). This is only available when querying single events (i.e. the parameter singleEvents is True)",
|
|
2394
|
+ "Order by last modification time (ascending)."
|
|
2395
|
+ ],
|
|
2396
|
+ "location": "query"
|
|
2397
|
+ },
|
|
2398
|
+ "pageToken": {
|
|
2399
|
+ "type": "string",
|
|
2400
|
+ "description": "Token specifying which result page to return. Optional.",
|
|
2401
|
+ "location": "query"
|
|
2402
|
+ },
|
|
2403
|
+ "privateExtendedProperty": {
|
|
2404
|
+ "type": "string",
|
|
2405
|
+ "description": "Extended properties constraint specified as propertyName=value. Matches only private properties. This parameter might be repeated multiple times to return events that match all given constraints.",
|
|
2406
|
+ "repeated": true,
|
|
2407
|
+ "location": "query"
|
|
2408
|
+ },
|
|
2409
|
+ "q": {
|
|
2410
|
+ "type": "string",
|
|
2411
|
+ "description": "Free text search terms to find events that match these terms in any field, except for extended properties. Optional.",
|
|
2412
|
+ "location": "query"
|
|
2413
|
+ },
|
|
2414
|
+ "sharedExtendedProperty": {
|
|
2415
|
+ "type": "string",
|
|
2416
|
+ "description": "Extended properties constraint specified as propertyName=value. Matches only shared properties. This parameter might be repeated multiple times to return events that match all given constraints.",
|
|
2417
|
+ "repeated": true,
|
|
2418
|
+ "location": "query"
|
|
2419
|
+ },
|
|
2420
|
+ "showDeleted": {
|
|
2421
|
+ "type": "boolean",
|
|
2422
|
+ "description": "Whether to include deleted events (with status equals \"cancelled\") in the result. Cancelled instances of recurring events (but not the underlying recurring event) will still be included if showDeleted and singleEvents are both False. If showDeleted and singleEvents are both True, only single instances of deleted events (but not the underlying recurring events) are returned. Optional. The default is False.",
|
|
2423
|
+ "location": "query"
|
|
2424
|
+ },
|
|
2425
|
+ "showHiddenInvitations": {
|
|
2426
|
+ "type": "boolean",
|
|
2427
|
+ "description": "Whether to include hidden invitations in the result. Optional. The default is False.",
|
|
2428
|
+ "location": "query"
|
|
2429
|
+ },
|
|
2430
|
+ "singleEvents": {
|
|
2431
|
+ "type": "boolean",
|
|
2432
|
+ "description": "Whether to expand recurring events into instances and only return single one-off events and instances of recurring events, but not the underlying recurring events themselves. Optional. The default is False.",
|
|
2433
|
+ "location": "query"
|
|
2434
|
+ },
|
|
2435
|
+ "syncToken": {
|
|
2436
|
+ "type": "string",
|
|
2437
|
+ "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then. All events deleted since the previous list request will always be in the result set and it is not allowed to set showDeleted to False.\nThere are several query parameters that cannot be specified together with nextSyncToken to ensure consistency of the client state.\n\nThese are: \n- iCalUID \n- orderBy \n- privateExtendedProperty \n- q \n- sharedExtendedProperty \n- timeMin \n- timeMax \n- updatedMin If the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
|
|
2438
|
+ "location": "query"
|
|
2439
|
+ },
|
|
2440
|
+ "timeMax": {
|
|
2441
|
+ "type": "string",
|
|
2442
|
+ "description": "Upper bound (exclusive) for an event's start time to filter by. Optional. The default is not to filter by start time.",
|
|
2443
|
+ "format": "date-time",
|
|
2444
|
+ "location": "query"
|
|
2445
|
+ },
|
|
2446
|
+ "timeMin": {
|
|
2447
|
+ "type": "string",
|
|
2448
|
+ "description": "Lower bound (inclusive) for an event's end time to filter by. Optional. The default is not to filter by end time.",
|
|
2449
|
+ "format": "date-time",
|
|
2450
|
+ "location": "query"
|
|
2451
|
+ },
|
|
2452
|
+ "timeZone": {
|
|
2453
|
+ "type": "string",
|
|
2454
|
+ "description": "Time zone used in the response. Optional. The default is the time zone of the calendar.",
|
|
2455
|
+ "location": "query"
|
|
2456
|
+ },
|
|
2457
|
+ "updatedMin": {
|
|
2458
|
+ "type": "string",
|
|
2459
|
+ "description": "Lower bound for an event's last modification time (as a RFC 3339 timestamp) to filter by. When specified, entries deleted since this time will always be included regardless of showDeleted. Optional. The default is not to filter by last modification time.",
|
|
2460
|
+ "format": "date-time",
|
|
2461
|
+ "location": "query"
|
|
2462
|
+ }
|
|
2463
|
+ },
|
|
2464
|
+ "parameterOrder": [
|
|
2465
|
+ "calendarId"
|
|
2466
|
+ ],
|
|
2467
|
+ "request": {
|
|
2468
|
+ "$ref": "Channel",
|
|
2469
|
+ "parameterName": "resource"
|
|
2470
|
+ },
|
|
2471
|
+ "response": {
|
|
2472
|
+ "$ref": "Channel"
|
|
2473
|
+ },
|
|
2474
|
+ "scopes": [
|
|
2475
|
+ "https://www.googleapis.com/auth/calendar",
|
|
2476
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
2477
|
+ ],
|
|
2478
|
+ "supportsSubscription": true
|
|
2479
|
+ }
|
|
2480
|
+ }
|
|
2481
|
+ },
|
|
2482
|
+ "freebusy": {
|
|
2483
|
+ "methods": {
|
|
2484
|
+ "query": {
|
|
2485
|
+ "id": "calendar.freebusy.query",
|
|
2486
|
+ "path": "freeBusy",
|
|
2487
|
+ "httpMethod": "POST",
|
|
2488
|
+ "description": "Returns free/busy information for a set of calendars.",
|
|
2489
|
+ "request": {
|
|
2490
|
+ "$ref": "FreeBusyRequest"
|
|
2491
|
+ },
|
|
2492
|
+ "response": {
|
|
2493
|
+ "$ref": "FreeBusyResponse"
|
|
2494
|
+ },
|
|
2495
|
+ "scopes": [
|
|
2496
|
+ "https://www.googleapis.com/auth/calendar",
|
|
2497
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
2498
|
+ ]
|
|
2499
|
+ }
|
|
2500
|
+ }
|
|
2501
|
+ },
|
|
2502
|
+ "settings": {
|
|
2503
|
+ "methods": {
|
|
2504
|
+ "get": {
|
|
2505
|
+ "id": "calendar.settings.get",
|
|
2506
|
+ "path": "users/me/settings/{setting}",
|
|
2507
|
+ "httpMethod": "GET",
|
|
2508
|
+ "description": "Returns a single user setting.",
|
|
2509
|
+ "parameters": {
|
|
2510
|
+ "setting": {
|
|
2511
|
+ "type": "string",
|
|
2512
|
+ "description": "The id of the user setting.",
|
|
2513
|
+ "required": true,
|
|
2514
|
+ "location": "path"
|
|
2515
|
+ }
|
|
2516
|
+ },
|
|
2517
|
+ "parameterOrder": [
|
|
2518
|
+ "setting"
|
|
2519
|
+ ],
|
|
2520
|
+ "response": {
|
|
2521
|
+ "$ref": "Setting"
|
|
2522
|
+ },
|
|
2523
|
+ "scopes": [
|
|
2524
|
+ "https://www.googleapis.com/auth/calendar",
|
|
2525
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
2526
|
+ ]
|
|
2527
|
+ },
|
|
2528
|
+ "list": {
|
|
2529
|
+ "id": "calendar.settings.list",
|
|
2530
|
+ "path": "users/me/settings",
|
|
2531
|
+ "httpMethod": "GET",
|
|
2532
|
+ "description": "Returns all user settings for the authenticated user.",
|
|
2533
|
+ "parameters": {
|
|
2534
|
+ "maxResults": {
|
|
2535
|
+ "type": "integer",
|
|
2536
|
+ "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
|
|
2537
|
+ "format": "int32",
|
|
2538
|
+ "minimum": "1",
|
|
2539
|
+ "location": "query"
|
|
2540
|
+ },
|
|
2541
|
+ "pageToken": {
|
|
2542
|
+ "type": "string",
|
|
2543
|
+ "description": "Token specifying which result page to return. Optional.",
|
|
2544
|
+ "location": "query"
|
|
2545
|
+ },
|
|
2546
|
+ "syncToken": {
|
|
2547
|
+ "type": "string",
|
|
2548
|
+ "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
|
|
2549
|
+ "location": "query"
|
|
2550
|
+ }
|
|
2551
|
+ },
|
|
2552
|
+ "response": {
|
|
2553
|
+ "$ref": "Settings"
|
|
2554
|
+ },
|
|
2555
|
+ "scopes": [
|
|
2556
|
+ "https://www.googleapis.com/auth/calendar",
|
|
2557
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
2558
|
+ ],
|
|
2559
|
+ "supportsSubscription": true
|
|
2560
|
+ },
|
|
2561
|
+ "watch": {
|
|
2562
|
+ "id": "calendar.settings.watch",
|
|
2563
|
+ "path": "users/me/settings/watch",
|
|
2564
|
+ "httpMethod": "POST",
|
|
2565
|
+ "description": "Watch for changes to Settings resources.",
|
|
2566
|
+ "parameters": {
|
|
2567
|
+ "maxResults": {
|
|
2568
|
+ "type": "integer",
|
|
2569
|
+ "description": "Maximum number of entries returned on one result page. By default the value is 100 entries. The page size can never be larger than 250 entries. Optional.",
|
|
2570
|
+ "format": "int32",
|
|
2571
|
+ "minimum": "1",
|
|
2572
|
+ "location": "query"
|
|
2573
|
+ },
|
|
2574
|
+ "pageToken": {
|
|
2575
|
+ "type": "string",
|
|
2576
|
+ "description": "Token specifying which result page to return. Optional.",
|
|
2577
|
+ "location": "query"
|
|
2578
|
+ },
|
|
2579
|
+ "syncToken": {
|
|
2580
|
+ "type": "string",
|
|
2581
|
+ "description": "Token obtained from the nextSyncToken field returned on the last page of results from the previous list request. It makes the result of this list request contain only entries that have changed since then.\nIf the syncToken expires, the server will respond with a 410 GONE response code and the client should clear its storage and perform a full synchronization without any syncToken.\nLearn more about incremental synchronization.\nOptional. The default is to return all entries.",
|
|
2582
|
+ "location": "query"
|
|
2583
|
+ }
|
|
2584
|
+ },
|
|
2585
|
+ "request": {
|
|
2586
|
+ "$ref": "Channel",
|
|
2587
|
+ "parameterName": "resource"
|
|
2588
|
+ },
|
|
2589
|
+ "response": {
|
|
2590
|
+ "$ref": "Channel"
|
|
2591
|
+ },
|
|
2592
|
+ "scopes": [
|
|
2593
|
+ "https://www.googleapis.com/auth/calendar",
|
|
2594
|
+ "https://www.googleapis.com/auth/calendar.readonly"
|
|
2595
|
+ ],
|
|
2596
|
+ "supportsSubscription": true
|
|
2597
|
+ }
|
|
2598
|
+ }
|
|
2599
|
+ }
|
|
2600
|
+ }
|
|
2601
|
+}
|
|
|
@@ -10,8 +10,8 @@ jane_website_agent:
|
10
|
10
|
:expected_update_period_in_days => 2,
|
11
|
11
|
:mode => :on_change,
|
12
|
12
|
:extract => {
|
13
|
|
- :title => {:css => "item title", :text => true},
|
14
|
|
- :url => {:css => "item link", :text => true}
|
|
13
|
+ :title => {:css => "item title", :value => './/text()'},
|
|
14
|
+ :url => {:css => "item link", :value => './/text()'}
|
15
|
15
|
}
|
16
|
16
|
}.to_json.inspect %>
|
17
|
17
|
|
|
|
@@ -27,8 +27,8 @@ bob_website_agent:
|
27
|
27
|
:expected_update_period_in_days => 2,
|
28
|
28
|
:mode => :on_change,
|
29
|
29
|
:extract => {
|
30
|
|
- :url => {:css => "#comic img", :attr => "src"},
|
31
|
|
- :title => {:css => "#comic img", :attr => "title"}
|
|
30
|
+ :url => {:css => "#comic img", :value => "@src"},
|
|
31
|
+ :title => {:css => "#comic img", :value => "@title"}
|
32
|
32
|
}
|
33
|
33
|
}.to_json.inspect %>
|
34
|
34
|
|
|
|
@@ -1,12 +1,6 @@
|
1
|
1
|
require 'spec_helper'
|
2
|
2
|
|
3
|
3
|
describe DotHelper do
|
4
|
|
- describe "#dot_id" do
|
5
|
|
- it "properly escapes double quotaion and backslash" do
|
6
|
|
- dot_id('hello\\"').should == '"hello\\\\\\""'
|
7
|
|
- end
|
8
|
|
- end
|
9
|
|
-
|
10
|
4
|
describe "with example Agents" do
|
11
|
5
|
class Agents::DotFoo < Agent
|
12
|
6
|
default_schedule "2pm"
|
|
|
@@ -30,18 +24,77 @@ describe DotHelper do
|
30
|
24
|
end
|
31
|
25
|
|
32
|
26
|
describe "#agents_dot" do
|
|
27
|
+ before do
|
|
28
|
+ @agents = [
|
|
29
|
+ @foo = Agents::DotFoo.new(name: "foo").tap { |agent|
|
|
30
|
+ agent.user = users(:bob)
|
|
31
|
+ agent.save!
|
|
32
|
+ },
|
|
33
|
+
|
|
34
|
+ @bar1 = Agents::DotBar.new(name: "bar1").tap { |agent|
|
|
35
|
+ agent.user = users(:bob)
|
|
36
|
+ agent.sources << @foo
|
|
37
|
+ agent.save!
|
|
38
|
+ },
|
|
39
|
+
|
|
40
|
+ @bar2 = Agents::DotBar.new(name: "bar2").tap { |agent|
|
|
41
|
+ agent.user = users(:bob)
|
|
42
|
+ agent.sources << @foo
|
|
43
|
+ agent.propagate_immediately = true
|
|
44
|
+ agent.disabled = true
|
|
45
|
+ agent.save!
|
|
46
|
+ },
|
|
47
|
+
|
|
48
|
+ @bar3 = Agents::DotBar.new(name: "bar3").tap { |agent|
|
|
49
|
+ agent.user = users(:bob)
|
|
50
|
+ agent.sources << @bar2
|
|
51
|
+ agent.save!
|
|
52
|
+ },
|
|
53
|
+ ]
|
|
54
|
+ end
|
|
55
|
+
|
33
|
56
|
it "generates a DOT script" do
|
34
|
|
- @foo = Agents::DotFoo.new(:name => "foo")
|
35
|
|
- @foo.user = users(:bob)
|
36
|
|
- @foo.save!
|
|
57
|
+ agents_dot(@agents).should =~ %r{
|
|
58
|
+ \A
|
|
59
|
+ digraph \s foo \{
|
|
60
|
+ node \[ [^\]]+ \];
|
|
61
|
+ (?<foo>\w+) \[label=foo\];
|
|
62
|
+ \k<foo> -> (?<bar1>\w+) \[style=dashed\];
|
|
63
|
+ \k<foo> -> (?<bar2>\w+) \[color="\#999999"\];
|
|
64
|
+ \k<bar1> \[label=bar1\];
|
|
65
|
+ \k<bar2> \[label="bar2 \s \(Disabled\)",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
|
66
|
+ \k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\];
|
|
67
|
+ \k<bar3> \[label=bar3\];
|
|
68
|
+ \}
|
|
69
|
+ \z
|
|
70
|
+ }x
|
|
71
|
+ end
|
37
|
72
|
|
38
|
|
- @bar = Agents::DotBar.new(:name => "bar")
|
39
|
|
- @bar.user = users(:bob)
|
40
|
|
- @bar.sources << @foo
|
41
|
|
- @bar.save!
|
|
73
|
+ it "generates a richer DOT script" do
|
|
74
|
+ agents_dot(@agents, true).should =~ %r{
|
|
75
|
+ \A
|
|
76
|
+ digraph \s foo \{
|
|
77
|
+ node \[ [^\]]+ \];
|
|
78
|
+ (?<foo>\w+) \[label=foo,URL="#{Regexp.quote(agent_path(@foo))}"\];
|
|
79
|
+ \k<foo> -> (?<bar1>\w+) \[style=dashed\];
|
|
80
|
+ \k<foo> -> (?<bar2>\w+) \[color="\#999999"\];
|
|
81
|
+ \k<bar1> \[label=bar1,URL="#{Regexp.quote(agent_path(@bar1))}"\];
|
|
82
|
+ \k<bar2> \[label="bar2 \s \(Disabled\)",URL="#{Regexp.quote(agent_path(@bar2))}",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
|
83
|
+ \k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\];
|
|
84
|
+ \k<bar3> \[label=bar3,URL="#{Regexp.quote(agent_path(@bar3))}"\];
|
|
85
|
+ \}
|
|
86
|
+ \z
|
|
87
|
+ }x
|
|
88
|
+ end
|
|
89
|
+ end
|
|
90
|
+ end
|
42
|
91
|
|
43
|
|
- agents_dot([@foo, @bar]).should == 'digraph foo {"foo";"foo"->"bar";"bar";}'
|
44
|
|
- agents_dot([@foo, @bar], true).should == 'digraph foo {"foo"[URL="/agents/%d"];"foo"->"bar";"bar"[URL="/agents/%d"];}' % [@foo.id, @bar.id]
|
|
92
|
+ describe DotHelper::DotDrawer do
|
|
93
|
+ describe "#id" do
|
|
94
|
+ it "properly escapes double quotaion and backslash" do
|
|
95
|
+ DotHelper::DotDrawer.draw(foo: "") {
|
|
96
|
+ id('hello\\"')
|
|
97
|
+ }.should == '"hello\\\\\\""'
|
45
|
98
|
end
|
46
|
99
|
end
|
47
|
100
|
end
|
|
|
@@ -22,8 +22,8 @@ describe Utils do
|
22
|
22
|
|
23
|
23
|
Utils.unindent("Hello\n I am indented").should == "Hello\n I am indented"
|
24
|
24
|
|
25
|
|
- a = " Events will have the fields you specified. Your options look like:\n\n {\n \"url\": {\n \"css\": \"#comic img\",\n \"attr\": \"src\"\n },\n \"title\": {\n \"css\": \"#comic img\",\n \"attr\": \"title\"\n }\n }\"\n"
|
26
|
|
- Utils.unindent(a).should == "Events will have the fields you specified. Your options look like:\n\n {\n \"url\": {\n\"css\": \"#comic img\",\n\"attr\": \"src\"\n },\n \"title\": {\n\"css\": \"#comic img\",\n\"attr\": \"title\"\n }\n }\""
|
|
25
|
+ a = " Events will have the fields you specified. Your options look like:\n\n {\n \"url\": {\n \"css\": \"#comic img\",\n \"value\": \"@src\"\n },\n \"title\": {\n \"css\": \"#comic img\",\n \"value\": \"@title\"\n }\n }\"\n"
|
|
26
|
+ Utils.unindent(a).should == "Events will have the fields you specified. Your options look like:\n\n {\n \"url\": {\n\"css\": \"#comic img\",\n\"value\": \"@src\"\n },\n \"title\": {\n\"css\": \"#comic img\",\n\"value\": \"@title\"\n }\n }\""
|
27
|
27
|
end
|
28
|
28
|
end
|
29
|
29
|
|
|
|
@@ -114,4 +114,4 @@ describe Utils do
|
114
|
114
|
cleaned_json.should include("<\\/script>")
|
115
|
115
|
end
|
116
|
116
|
end
|
117
|
|
-end
|
|
117
|
+end
|
|
|
@@ -132,6 +132,13 @@ describe Agent do
|
132
|
132
|
it_behaves_like HasGuid
|
133
|
133
|
end
|
134
|
134
|
|
|
135
|
+ describe ".short_type" do
|
|
136
|
+ it "returns a short name without 'Agents::'" do
|
|
137
|
+ Agents::SomethingSource.new.short_type.should == "SomethingSource"
|
|
138
|
+ Agents::CannotBeScheduled.new.short_type.should == "CannotBeScheduled"
|
|
139
|
+ end
|
|
140
|
+ end
|
|
141
|
+
|
135
|
142
|
describe ".default_schedule" do
|
136
|
143
|
it "stores the default on the class" do
|
137
|
144
|
Agents::SomethingSource.default_schedule.should == "2pm"
|
|
|
@@ -729,3 +736,98 @@ describe Agent do
|
729
|
736
|
end
|
730
|
737
|
end
|
731
|
738
|
end
|
|
739
|
+
|
|
740
|
+describe AgentDrop do
|
|
741
|
+ def interpolate(string, agent)
|
|
742
|
+ agent.interpolate_string(string, "agent" => agent)
|
|
743
|
+ end
|
|
744
|
+
|
|
745
|
+ before do
|
|
746
|
+ @wsa1 = Agents::WebsiteAgent.new(
|
|
747
|
+ name: 'XKCD',
|
|
748
|
+ options: {
|
|
749
|
+ expected_update_period_in_days: 2,
|
|
750
|
+ type: 'html',
|
|
751
|
+ url: 'http://xkcd.com/',
|
|
752
|
+ mode: 'on_change',
|
|
753
|
+ extract: {
|
|
754
|
+ url: { css: '#comic img', value: '@src' },
|
|
755
|
+ title: { css: '#comic img', value: '@alt' },
|
|
756
|
+ },
|
|
757
|
+ },
|
|
758
|
+ schedule: 'every_1h',
|
|
759
|
+ keep_events_for: 2)
|
|
760
|
+ @wsa1.user = users(:bob)
|
|
761
|
+ @wsa1.save!
|
|
762
|
+
|
|
763
|
+ @wsa2 = Agents::WebsiteAgent.new(
|
|
764
|
+ name: 'Dilbert',
|
|
765
|
+ options: {
|
|
766
|
+ expected_update_period_in_days: 2,
|
|
767
|
+ type: 'html',
|
|
768
|
+ url: 'http://dilbert.com/',
|
|
769
|
+ mode: 'on_change',
|
|
770
|
+ extract: {
|
|
771
|
+ url: { css: '[id^=strip_enlarged_] img', value: '@src' },
|
|
772
|
+ title: { css: '.STR_DateStrip', value: './/text()' },
|
|
773
|
+ },
|
|
774
|
+ },
|
|
775
|
+ schedule: 'every_12h',
|
|
776
|
+ keep_events_for: 2)
|
|
777
|
+ @wsa2.user = users(:bob)
|
|
778
|
+ @wsa2.save!
|
|
779
|
+
|
|
780
|
+ @efa = Agents::EventFormattingAgent.new(
|
|
781
|
+ name: 'Formatter',
|
|
782
|
+ options: {
|
|
783
|
+ instructions: {
|
|
784
|
+ message: '{{agent.name}}: {{title}} {{url}}',
|
|
785
|
+ agent: '{{agent.type}}',
|
|
786
|
+ },
|
|
787
|
+ mode: 'clean',
|
|
788
|
+ matchers: [],
|
|
789
|
+ skip_created_at: 'false',
|
|
790
|
+ },
|
|
791
|
+ keep_events_for: 2,
|
|
792
|
+ propagate_immediately: true)
|
|
793
|
+ @efa.user = users(:bob)
|
|
794
|
+ @efa.sources << @wsa1 << @wsa2
|
|
795
|
+ @efa.memory[:test] = 1
|
|
796
|
+ @efa.save!
|
|
797
|
+ end
|
|
798
|
+
|
|
799
|
+ it 'should be created via Agent#to_liquid' do
|
|
800
|
+ @wsa1.to_liquid.class.should be(AgentDrop)
|
|
801
|
+ @wsa2.to_liquid.class.should be(AgentDrop)
|
|
802
|
+ @efa.to_liquid.class.should be(AgentDrop)
|
|
803
|
+ end
|
|
804
|
+
|
|
805
|
+ it 'should have .type and .name' do
|
|
806
|
+ t = '{{agent.type}}: {{agent.name}}'
|
|
807
|
+ interpolate(t, @wsa1).should eq('WebsiteAgent: XKCD')
|
|
808
|
+ interpolate(t, @wsa2).should eq('WebsiteAgent: Dilbert')
|
|
809
|
+ interpolate(t, @efa).should eq('EventFormattingAgent: Formatter')
|
|
810
|
+ end
|
|
811
|
+
|
|
812
|
+ it 'should have .options' do
|
|
813
|
+ t = '{{agent.options.url}}'
|
|
814
|
+ interpolate(t, @wsa1).should eq('http://xkcd.com/')
|
|
815
|
+ interpolate(t, @wsa2).should eq('http://dilbert.com/')
|
|
816
|
+ interpolate('{{agent.options.instructions.message}}',
|
|
817
|
+ @efa).should eq('{{agent.name}}: {{title}} {{url}}')
|
|
818
|
+ end
|
|
819
|
+
|
|
820
|
+ it 'should have .sources' do
|
|
821
|
+ t = '{{agent.sources.size}}: {{agent.sources | map:"name" | join:", "}}'
|
|
822
|
+ interpolate(t, @wsa1).should eq('0: ')
|
|
823
|
+ interpolate(t, @wsa2).should eq('0: ')
|
|
824
|
+ interpolate(t, @efa).should eq('2: XKCD, Dilbert')
|
|
825
|
+ end
|
|
826
|
+
|
|
827
|
+ it 'should have .receivers' do
|
|
828
|
+ t = '{{agent.receivers.size}}: {{agent.receivers | map:"name" | join:", "}}'
|
|
829
|
+ interpolate(t, @wsa1).should eq('1: Formatter')
|
|
830
|
+ interpolate(t, @wsa2).should eq('1: Formatter')
|
|
831
|
+ interpolate(t, @efa).should eq('0: ')
|
|
832
|
+ end
|
|
833
|
+end
|
|
|
@@ -1,12 +1,14 @@
|
1
|
1
|
require 'spec_helper'
|
2
|
2
|
|
3
|
3
|
describe Agents::EmailAgent do
|
|
4
|
+ it_behaves_like EmailConcern
|
|
5
|
+
|
4
|
6
|
def get_message_part(mail, content_type)
|
5
|
7
|
mail.body.parts.find { |p| p.content_type.match content_type }.body.raw_source
|
6
|
8
|
end
|
7
|
9
|
|
8
|
10
|
before do
|
9
|
|
- @checker = Agents::EmailAgent.new(:name => "something", :options => { :expected_receive_period_in_days => 2, :subject => "something interesting" })
|
|
11
|
+ @checker = Agents::EmailAgent.new(:name => "something", :options => { :expected_receive_period_in_days => "2", :subject => "something interesting" })
|
10
|
12
|
@checker.user = users(:bob)
|
11
|
13
|
@checker.save!
|
12
|
14
|
end
|
|
|
@@ -54,6 +56,5 @@ describe Agents::EmailAgent do
|
54
|
56
|
plain_email_text.should =~ /avehumidity/
|
55
|
57
|
html_email_text.should =~ /avehumidity/
|
56
|
58
|
end
|
57
|
|
-
|
58
|
59
|
end
|
59
|
60
|
end
|
|
|
@@ -1,12 +1,14 @@
|
1
|
1
|
require 'spec_helper'
|
2
|
2
|
|
3
|
3
|
describe Agents::EmailDigestAgent do
|
|
4
|
+ it_behaves_like EmailConcern
|
|
5
|
+
|
4
|
6
|
def get_message_part(mail, content_type)
|
5
|
7
|
mail.body.parts.find { |p| p.content_type.match content_type }.body.raw_source
|
6
|
8
|
end
|
7
|
9
|
|
8
|
10
|
before do
|
9
|
|
- @checker = Agents::EmailDigestAgent.new(:name => "something", :options => { :expected_receive_period_in_days => 2, :subject => "something interesting" })
|
|
11
|
+ @checker = Agents::EmailDigestAgent.new(:name => "something", :options => { :expected_receive_period_in_days => "2", :subject => "something interesting" })
|
10
|
12
|
@checker.user = users(:bob)
|
11
|
13
|
@checker.save!
|
12
|
14
|
end
|
|
|
@@ -7,7 +7,8 @@ describe Agents::EventFormattingAgent do
|
7
|
7
|
:options => {
|
8
|
8
|
:instructions => {
|
9
|
9
|
:message => "Received {{content.text}} from {{content.name}} .",
|
10
|
|
- :subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}"
|
|
10
|
+ :subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}",
|
|
11
|
+ :agent => "{{agent.type}}",
|
11
|
12
|
},
|
12
|
13
|
:mode => "clean",
|
13
|
14
|
:matchers => [
|
|
|
@@ -17,7 +18,6 @@ describe Agents::EventFormattingAgent do
|
17
|
18
|
:to => "pretty_date",
|
18
|
19
|
},
|
19
|
20
|
],
|
20
|
|
- :skip_agent => "false",
|
21
|
21
|
:skip_created_at => "false"
|
22
|
22
|
}
|
23
|
23
|
}
|
|
|
@@ -53,14 +53,6 @@ describe Agents::EventFormattingAgent do
|
53
|
53
|
Event.last.payload[:content].should_not == nil
|
54
|
54
|
end
|
55
|
55
|
|
56
|
|
- it "should accept skip_agent" do
|
57
|
|
- @checker.receive([@event])
|
58
|
|
- Event.last.payload[:agent].should == "WeatherAgent"
|
59
|
|
- @checker.options[:skip_agent] = "true"
|
60
|
|
- @checker.receive([@event])
|
61
|
|
- Event.last.payload[:agent].should == nil
|
62
|
|
- end
|
63
|
|
-
|
64
|
56
|
it "should accept skip_created_at" do
|
65
|
57
|
@checker.receive([@event])
|
66
|
58
|
Event.last.payload[:created_at].should_not == nil
|
|
|
@@ -69,12 +61,13 @@ describe Agents::EventFormattingAgent do
|
69
|
61
|
Event.last.payload[:created_at].should == nil
|
70
|
62
|
end
|
71
|
63
|
|
72
|
|
- it "should handle JSONPaths in instructions" do
|
|
64
|
+ it "should handle Liquid templating in instructions" do
|
73
|
65
|
@checker.receive([@event])
|
74
|
66
|
Event.last.payload[:message].should == "Received Some Lorem Ipsum from somevalue ."
|
|
67
|
+ Event.last.payload[:agent].should == "WeatherAgent"
|
75
|
68
|
end
|
76
|
69
|
|
77
|
|
- it "should handle matchers and JSONPaths in instructions" do
|
|
70
|
+ it "should handle matchers and Liquid templating in instructions" do
|
78
|
71
|
@checker.receive([@event])
|
79
|
72
|
Event.last.payload[:subject].should == "Weather looks like someothervalue according to the forecast at 10:00 PM EST"
|
80
|
73
|
end
|
|
|
@@ -152,11 +145,6 @@ describe Agents::EventFormattingAgent do
|
152
|
145
|
@checker.should_not be_valid
|
153
|
146
|
end
|
154
|
147
|
|
155
|
|
- it "should validate presence of skip_agent" do
|
156
|
|
- @checker.options[:skip_agent] = ""
|
157
|
|
- @checker.should_not be_valid
|
158
|
|
- end
|
159
|
|
-
|
160
|
148
|
it "should validate presence of skip_created_at" do
|
161
|
149
|
@checker.options[:skip_created_at] = ""
|
162
|
150
|
@checker.should_not be_valid
|
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+require 'spec_helper'
|
|
2
|
+
|
|
3
|
+describe Agents::GoogleCalendarPublishAgent, :vcr do
|
|
4
|
+ before do
|
|
5
|
+ @valid_params = {
|
|
6
|
+ 'expected_update_period_in_days' => "10",
|
|
7
|
+ 'calendar_id' => 'sqv39gj35tc837gdns1g4d81cg@group.calendar.google.com',
|
|
8
|
+ 'google' => {
|
|
9
|
+ 'key_file' => File.dirname(__FILE__) + '/../../data_fixtures/private.key',
|
|
10
|
+ 'key_secret' => 'notasecret',
|
|
11
|
+ 'service_account_email' => '1029936966326-ncjd7776pcspc98hsg82gsb56t3217ef@developer.gserviceaccount.com'
|
|
12
|
+ }
|
|
13
|
+ }
|
|
14
|
+ @checker = Agents::GoogleCalendarPublishAgent.new(:name => "somename", :options => @valid_params)
|
|
15
|
+ @checker.user = users(:jane)
|
|
16
|
+ @checker.save!
|
|
17
|
+ end
|
|
18
|
+
|
|
19
|
+ describe '#receive' do
|
|
20
|
+ it 'should publish any payload it receives' do
|
|
21
|
+ event1 = Event.new
|
|
22
|
+ event1.agent = agents(:bob_manual_event_agent)
|
|
23
|
+ event1.payload = {
|
|
24
|
+ 'message' => {
|
|
25
|
+ 'visibility' => 'default',
|
|
26
|
+ 'summary' => "Awesome event",
|
|
27
|
+ 'description' => "An example event with text. Pro tip: DateTimes are in RFC3339",
|
|
28
|
+ 'end' => {
|
|
29
|
+ 'dateTime' => '2014-10-02T11:00:00-05:00'
|
|
30
|
+ },
|
|
31
|
+ 'start' => {
|
|
32
|
+ 'dateTime' => '2014-10-02T10:00:00-05:00'
|
|
33
|
+ }
|
|
34
|
+ }
|
|
35
|
+ }
|
|
36
|
+ event1.save!
|
|
37
|
+
|
|
38
|
+ @checker.receive([event1])
|
|
39
|
+
|
|
40
|
+ @checker.events.count.should eq(1)
|
|
41
|
+ end
|
|
42
|
+ end
|
|
43
|
+end
|
|
|
@@ -24,7 +24,7 @@ describe Agents::ImapFolderAgent do
|
24
|
24
|
end
|
25
|
25
|
|
26
|
26
|
def uidvalidity
|
27
|
|
- '100'
|
|
27
|
+ 100
|
28
|
28
|
end
|
29
|
29
|
|
30
|
30
|
def has_attachment?
|
|
|
@@ -53,7 +53,15 @@ describe Agents::ImapFolderAgent do
|
53
|
53
|
]
|
54
|
54
|
|
55
|
55
|
stub(@checker).each_unread_mail.returns { |yielder|
|
56
|
|
- @mails.each(&yielder)
|
|
56
|
+ seen = @checker.lastseen
|
|
57
|
+ notified = @checker.notified
|
|
58
|
+ @mails.each_with_object(notified) { |mail|
|
|
59
|
+ yielder[mail, notified]
|
|
60
|
+ seen[mail.uidvalidity] = mail.uid
|
|
61
|
+ }
|
|
62
|
+ @checker.lastseen = seen
|
|
63
|
+ @checker.notified = notified
|
|
64
|
+ nil
|
57
|
65
|
}
|
58
|
66
|
|
59
|
67
|
@payloads = [
|
|
|
@@ -110,11 +118,19 @@ describe Agents::ImapFolderAgent do
|
110
|
118
|
end
|
111
|
119
|
|
112
|
120
|
it 'should validate the boolean fields' do
|
113
|
|
- @checker.options['ssl'] = false
|
114
|
|
- @checker.should be_valid
|
|
121
|
+ %w[ssl mark_as_read].each do |key|
|
|
122
|
+ @checker.options[key] = 1
|
|
123
|
+ @checker.should_not be_valid
|
115
|
124
|
|
116
|
|
- @checker.options['ssl'] = 'true'
|
117
|
|
- @checker.should_not be_valid
|
|
125
|
+ @checker.options[key] = false
|
|
126
|
+ @checker.should be_valid
|
|
127
|
+
|
|
128
|
+ @checker.options[key] = 'true'
|
|
129
|
+ @checker.should be_valid
|
|
130
|
+
|
|
131
|
+ @checker.options[key] = ''
|
|
132
|
+ @checker.should be_valid
|
|
133
|
+ end
|
118
|
134
|
end
|
119
|
135
|
|
120
|
136
|
it 'should validate regexp conditions' do
|
|
|
@@ -139,9 +155,9 @@ describe Agents::ImapFolderAgent do
|
139
|
155
|
describe '#check' do
|
140
|
156
|
it 'should check for mails and save memory' do
|
141
|
157
|
lambda { @checker.check }.should change { Event.count }.by(2)
|
142
|
|
- @checker.memory['notified'].sort.should == @mails.map(&:message_id).sort
|
143
|
|
- @checker.memory['seen'].should == @mails.each_with_object({}) { |mail, seen|
|
144
|
|
- (seen[mail.uidvalidity] ||= []) << mail.uid
|
|
158
|
+ @checker.notified.sort.should == @mails.map(&:message_id).sort
|
|
159
|
+ @checker.lastseen.should == @mails.each_with_object(@checker.make_seen) { |mail, seen|
|
|
160
|
+ seen[mail.uidvalidity] = mail.uid
|
145
|
161
|
}
|
146
|
162
|
|
147
|
163
|
Event.last(2).map(&:payload) == @payloads
|
|
|
@@ -153,9 +169,9 @@ describe Agents::ImapFolderAgent do
|
153
|
169
|
@checker.options['conditions']['to'] = 'John.Doe@*'
|
154
|
170
|
|
155
|
171
|
lambda { @checker.check }.should change { Event.count }.by(1)
|
156
|
|
- @checker.memory['notified'].sort.should == [@mails.first.message_id]
|
157
|
|
- @checker.memory['seen'].should == @mails.each_with_object({}) { |mail, seen|
|
158
|
|
- (seen[mail.uidvalidity] ||= []) << mail.uid
|
|
172
|
+ @checker.notified.sort.should == [@mails.first.message_id]
|
|
173
|
+ @checker.lastseen.should == @mails.each_with_object(@checker.make_seen) { |mail, seen|
|
|
174
|
+ seen[mail.uidvalidity] = mail.uid
|
159
|
175
|
}
|
160
|
176
|
|
161
|
177
|
Event.last.payload.should == @payloads.first
|
|
|
@@ -170,9 +186,9 @@ describe Agents::ImapFolderAgent do
|
170
|
186
|
)
|
171
|
187
|
|
172
|
188
|
lambda { @checker.check }.should change { Event.count }.by(1)
|
173
|
|
- @checker.memory['notified'].sort.should == [@mails.last.message_id]
|
174
|
|
- @checker.memory['seen'].should == @mails.each_with_object({}) { |mail, seen|
|
175
|
|
- (seen[mail.uidvalidity] ||= []) << mail.uid
|
|
189
|
+ @checker.notified.sort.should == [@mails.last.message_id]
|
|
190
|
+ @checker.lastseen.should == @mails.each_with_object(@checker.make_seen) { |mail, seen|
|
|
191
|
+ seen[mail.uidvalidity] = mail.uid
|
176
|
192
|
}
|
177
|
193
|
|
178
|
194
|
Event.last.payload.should == @payloads.last.update(
|
|
|
@@ -208,9 +224,9 @@ describe Agents::ImapFolderAgent do
|
208
|
224
|
)
|
209
|
225
|
|
210
|
226
|
lambda { @checker.check }.should_not change { Event.count }
|
211
|
|
- @checker.memory['notified'].sort.should == []
|
212
|
|
- @checker.memory['seen'].should == @mails.each_with_object({}) { |mail, seen|
|
213
|
|
- (seen[mail.uidvalidity] ||= []) << mail.uid
|
|
227
|
+ @checker.notified.sort.should == []
|
|
228
|
+ @checker.lastseen.should == @mails.each_with_object(@checker.make_seen) { |mail, seen|
|
|
229
|
+ seen[mail.uidvalidity] = mail.uid
|
214
|
230
|
}
|
215
|
231
|
end
|
216
|
232
|
|
|
|
@@ -25,11 +25,25 @@ describe Agents::PostAgent do
|
25
|
25
|
'somekey' => 'value'
|
26
|
26
|
}
|
27
|
27
|
}
|
|
28
|
+ @requests = 0
|
|
29
|
+ @sent_requests = { Net::HTTP::Get => [], Net::HTTP::Post => [], Net::HTTP::Put => [], Net::HTTP::Delete => [], Net::HTTP::Patch => [] }
|
28
|
30
|
|
29
|
|
- @sent_posts = []
|
30
|
|
- @sent_gets = []
|
31
|
|
- stub.any_instance_of(Agents::PostAgent).post_data { |data| @sent_posts << data }
|
32
|
|
- stub.any_instance_of(Agents::PostAgent).get_data { |data| @sent_gets << data }
|
|
31
|
+ stub.any_instance_of(Agents::PostAgent).post_data { |data, payload, type| @requests += 1; @sent_requests[type] << data }
|
|
32
|
+ stub.any_instance_of(Agents::PostAgent).get_data { |data, payload| @requests += 1; @sent_requests[Net::HTTP::Get] << data }
|
|
33
|
+ end
|
|
34
|
+
|
|
35
|
+ describe "making requests" do
|
|
36
|
+ it "can make requests of each type" do
|
|
37
|
+ { 'get' => Net::HTTP::Get, 'put' => Net::HTTP::Put,
|
|
38
|
+ 'post' => Net::HTTP::Post, 'patch' => Net::HTTP::Patch,
|
|
39
|
+ 'delete' => Net::HTTP::Delete }.each.with_index do |(verb, type), index|
|
|
40
|
+ @checker.options['method'] = verb
|
|
41
|
+ @checker.should be_valid
|
|
42
|
+ @checker.check
|
|
43
|
+ @requests.should == index + 1
|
|
44
|
+ @sent_requests[type].length.should == 1
|
|
45
|
+ end
|
|
46
|
+ end
|
33
|
47
|
end
|
34
|
48
|
|
35
|
49
|
describe "#receive" do
|
|
|
@@ -45,11 +59,11 @@ describe Agents::PostAgent do
|
45
|
59
|
lambda {
|
46
|
60
|
lambda {
|
47
|
61
|
@checker.receive([@event, event1])
|
48
|
|
- }.should change { @sent_posts.length }.by(2)
|
49
|
|
- }.should_not change { @sent_gets.length }
|
|
62
|
+ }.should change { @sent_requests[Net::HTTP::Post].length }.by(2)
|
|
63
|
+ }.should_not change { @sent_requests[Net::HTTP::Get].length }
|
50
|
64
|
|
51
|
|
- @sent_posts[0].should == @event.payload.merge('default' => 'value')
|
52
|
|
- @sent_posts[1].should == event1.payload
|
|
65
|
+ @sent_requests[Net::HTTP::Post][0].should == @event.payload.merge('default' => 'value')
|
|
66
|
+ @sent_requests[Net::HTTP::Post][1].should == event1.payload
|
53
|
67
|
end
|
54
|
68
|
|
55
|
69
|
it "can make GET requests" do
|
|
|
@@ -58,10 +72,19 @@ describe Agents::PostAgent do
|
58
|
72
|
lambda {
|
59
|
73
|
lambda {
|
60
|
74
|
@checker.receive([@event])
|
61
|
|
- }.should change { @sent_gets.length }.by(1)
|
62
|
|
- }.should_not change { @sent_posts.length }
|
|
75
|
+ }.should change { @sent_requests[Net::HTTP::Get].length }.by(1)
|
|
76
|
+ }.should_not change { @sent_requests[Net::HTTP::Post].length }
|
|
77
|
+
|
|
78
|
+ @sent_requests[Net::HTTP::Get][0].should == @event.payload.merge('default' => 'value')
|
|
79
|
+ end
|
63
|
80
|
|
64
|
|
- @sent_gets[0].should == @event.payload.merge('default' => 'value')
|
|
81
|
+ it "can skip merging the incoming event when no_merge is set, but it still interpolates" do
|
|
82
|
+ @checker.options['no_merge'] = 'true'
|
|
83
|
+ @checker.options['payload'] = {
|
|
84
|
+ 'key' => 'it said: {{ someotherkey.somekey }}'
|
|
85
|
+ }
|
|
86
|
+ @checker.receive([@event])
|
|
87
|
+ @sent_requests[Net::HTTP::Post].first.should == { 'key' => 'it said: value' }
|
65
|
88
|
end
|
66
|
89
|
end
|
67
|
90
|
|
|
|
@@ -69,9 +92,9 @@ describe Agents::PostAgent do
|
69
|
92
|
it "sends options['payload'] as a POST request" do
|
70
|
93
|
lambda {
|
71
|
94
|
@checker.check
|
72
|
|
- }.should change { @sent_posts.length }.by(1)
|
|
95
|
+ }.should change { @sent_requests[Net::HTTP::Post].length }.by(1)
|
73
|
96
|
|
74
|
|
- @sent_posts[0].should == @checker.options['payload']
|
|
97
|
+ @sent_requests[Net::HTTP::Post][0].should == @checker.options['payload']
|
75
|
98
|
end
|
76
|
99
|
|
77
|
100
|
it "sends options['payload'] as a GET request" do
|
|
|
@@ -79,10 +102,10 @@ describe Agents::PostAgent do
|
79
|
102
|
lambda {
|
80
|
103
|
lambda {
|
81
|
104
|
@checker.check
|
82
|
|
- }.should change { @sent_gets.length }.by(1)
|
83
|
|
- }.should_not change { @sent_posts.length }
|
|
105
|
+ }.should change { @sent_requests[Net::HTTP::Get].length }.by(1)
|
|
106
|
+ }.should_not change { @sent_requests[Net::HTTP::Post].length }
|
84
|
107
|
|
85
|
|
- @sent_gets[0].should == @checker.options['payload']
|
|
108
|
+ @sent_requests[Net::HTTP::Get][0].should == @checker.options['payload']
|
86
|
109
|
end
|
87
|
110
|
end
|
88
|
111
|
|
|
|
@@ -112,7 +135,7 @@ describe Agents::PostAgent do
|
112
|
135
|
@checker.should_not be_valid
|
113
|
136
|
end
|
114
|
137
|
|
115
|
|
- it "should validate method as post or get, defaulting to post" do
|
|
138
|
+ it "should validate method as post, get, put, patch, or delete, defaulting to post" do
|
116
|
139
|
@checker.options['method'] = ""
|
117
|
140
|
@checker.method.should == "post"
|
118
|
141
|
@checker.should be_valid
|
|
|
@@ -125,11 +148,35 @@ describe Agents::PostAgent do
|
125
|
148
|
@checker.method.should == "get"
|
126
|
149
|
@checker.should be_valid
|
127
|
150
|
|
|
151
|
+ @checker.options['method'] = "patch"
|
|
152
|
+ @checker.method.should == "patch"
|
|
153
|
+ @checker.should be_valid
|
|
154
|
+
|
128
|
155
|
@checker.options['method'] = "wut"
|
129
|
156
|
@checker.method.should == "wut"
|
130
|
157
|
@checker.should_not be_valid
|
131
|
158
|
end
|
132
|
159
|
|
|
160
|
+ it "should validate that no_merge is 'true' or 'false', if present" do
|
|
161
|
+ @checker.options['no_merge'] = ""
|
|
162
|
+ @checker.should be_valid
|
|
163
|
+
|
|
164
|
+ @checker.options['no_merge'] = "true"
|
|
165
|
+ @checker.should be_valid
|
|
166
|
+
|
|
167
|
+ @checker.options['no_merge'] = "false"
|
|
168
|
+ @checker.should be_valid
|
|
169
|
+
|
|
170
|
+ @checker.options['no_merge'] = false
|
|
171
|
+ @checker.should be_valid
|
|
172
|
+
|
|
173
|
+ @checker.options['no_merge'] = true
|
|
174
|
+ @checker.should be_valid
|
|
175
|
+
|
|
176
|
+ @checker.options['no_merge'] = 'blarg'
|
|
177
|
+ @checker.should_not be_valid
|
|
178
|
+ end
|
|
179
|
+
|
133
|
180
|
it "should validate payload as a hash, if present" do
|
134
|
181
|
@checker.options['payload'] = ""
|
135
|
182
|
@checker.should be_valid
|
|
|
@@ -178,7 +225,17 @@ describe Agents::PostAgent do
|
178
|
225
|
it "just returns the post_uri when no params are given" do
|
179
|
226
|
@checker.options['post_url'] = "http://example.com/a/path?existing_param=existing_value"
|
180
|
227
|
uri = @checker.generate_uri
|
|
228
|
+ uri.host.should == 'example.com'
|
|
229
|
+ uri.scheme.should == 'http'
|
181
|
230
|
uri.request_uri.should == "/a/path?existing_param=existing_value"
|
182
|
231
|
end
|
|
232
|
+
|
|
233
|
+ it "interpolates when receiving a payload" do
|
|
234
|
+ @checker.options['post_url'] = "https://{{ domain }}/{{ variable }}?existing_param=existing_value"
|
|
235
|
+ uri = @checker.generate_uri({ "some_param" => "some_value", "another_param" => "another_value" }, { 'domain' => 'google.com', 'variable' => 'a_variable' })
|
|
236
|
+ uri.request_uri.should == "/a_variable?existing_param=existing_value&some_param=some_value&another_param=another_value"
|
|
237
|
+ uri.host.should == 'google.com'
|
|
238
|
+ uri.scheme.should == 'https'
|
|
239
|
+ end
|
183
|
240
|
end
|
184
|
241
|
end
|
|
|
@@ -0,0 +1,81 @@
|
|
1
|
+require 'spec_helper'
|
|
2
|
+
|
|
3
|
+describe Agents::RssAgent do
|
|
4
|
+ before do
|
|
5
|
+ @valid_options = {
|
|
6
|
+ 'expected_update_period_in_days' => "2",
|
|
7
|
+ 'url' => "https://github.com/cantino/huginn/commits/master.atom",
|
|
8
|
+ }
|
|
9
|
+
|
|
10
|
+ stub_request(:any, /github.com/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/github_rss.atom")), :status => 200)
|
|
11
|
+ end
|
|
12
|
+
|
|
13
|
+ let(:agent) do
|
|
14
|
+ _agent = Agents::RssAgent.new(:name => "github rss feed", :options => @valid_options)
|
|
15
|
+ _agent.user = users(:bob)
|
|
16
|
+ _agent.save!
|
|
17
|
+ _agent
|
|
18
|
+ end
|
|
19
|
+
|
|
20
|
+ it_behaves_like WebRequestConcern
|
|
21
|
+
|
|
22
|
+ describe "validations" do
|
|
23
|
+ it "should validate the presence of url" do
|
|
24
|
+ agent.options['url'] = "http://google.com"
|
|
25
|
+ agent.should be_valid
|
|
26
|
+
|
|
27
|
+ agent.options['url'] = ""
|
|
28
|
+ agent.should_not be_valid
|
|
29
|
+
|
|
30
|
+ agent.options['url'] = nil
|
|
31
|
+ agent.should_not be_valid
|
|
32
|
+ end
|
|
33
|
+
|
|
34
|
+ it "should validate the presence and numericality of expected_update_period_in_days" do
|
|
35
|
+ agent.options['expected_update_period_in_days'] = "5"
|
|
36
|
+ agent.should be_valid
|
|
37
|
+
|
|
38
|
+ agent.options['expected_update_period_in_days'] = "wut?"
|
|
39
|
+ agent.should_not be_valid
|
|
40
|
+
|
|
41
|
+ agent.options['expected_update_period_in_days'] = 0
|
|
42
|
+ agent.should_not be_valid
|
|
43
|
+
|
|
44
|
+ agent.options['expected_update_period_in_days'] = nil
|
|
45
|
+ agent.should_not be_valid
|
|
46
|
+
|
|
47
|
+ agent.options['expected_update_period_in_days'] = ""
|
|
48
|
+ agent.should_not be_valid
|
|
49
|
+ end
|
|
50
|
+ end
|
|
51
|
+
|
|
52
|
+ describe "emitting RSS events" do
|
|
53
|
+ it "should emit items as events" do
|
|
54
|
+ lambda {
|
|
55
|
+ agent.check
|
|
56
|
+ }.should change { agent.events.count }.by(20)
|
|
57
|
+ end
|
|
58
|
+
|
|
59
|
+ it "should track ids and not re-emit the same item when seen again" do
|
|
60
|
+ agent.check
|
|
61
|
+ agent.memory['seen_ids'].should == agent.events.map {|e| e.payload['id'] }
|
|
62
|
+
|
|
63
|
+ newest_id = agent.memory['seen_ids'][0]
|
|
64
|
+ agent.events.first.payload['id'].should == newest_id
|
|
65
|
+ agent.memory['seen_ids'] = agent.memory['seen_ids'][1..-1] # forget the newest id
|
|
66
|
+
|
|
67
|
+ lambda {
|
|
68
|
+ agent.check
|
|
69
|
+ }.should change { agent.events.count }.by(1)
|
|
70
|
+
|
|
71
|
+ agent.events.first.payload['id'].should == newest_id
|
|
72
|
+ agent.memory['seen_ids'][0].should == newest_id
|
|
73
|
+ end
|
|
74
|
+
|
|
75
|
+ it "should truncate the seen_ids in memory at 500 items" do
|
|
76
|
+ agent.memory['seen_ids'] = ['x'] * 490
|
|
77
|
+ agent.check
|
|
78
|
+ agent.memory['seen_ids'].length.should == 500
|
|
79
|
+ end
|
|
80
|
+ end
|
|
81
|
+end
|
|
|
@@ -4,23 +4,25 @@ describe Agents::WebsiteAgent do
|
4
|
4
|
describe "checking without basic auth" do
|
5
|
5
|
before do
|
6
|
6
|
stub_request(:any, /xkcd/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), :status => 200)
|
7
|
|
- @site = {
|
|
7
|
+ @valid_options = {
|
8
|
8
|
'name' => "XKCD",
|
9
|
|
- 'expected_update_period_in_days' => 2,
|
|
9
|
+ 'expected_update_period_in_days' => "2",
|
10
|
10
|
'type' => "html",
|
11
|
11
|
'url' => "http://xkcd.com",
|
12
|
12
|
'mode' => 'on_change',
|
13
|
13
|
'extract' => {
|
14
|
|
- 'url' => { 'css' => "#comic img", 'attr' => "src" },
|
15
|
|
- 'title' => { 'css' => "#comic img", 'attr' => "alt" },
|
16
|
|
- 'hovertext' => { 'css' => "#comic img", 'attr' => "title" }
|
|
14
|
+ 'url' => { 'css' => "#comic img", 'value' => "@src" },
|
|
15
|
+ 'title' => { 'css' => "#comic img", 'value' => "@alt" },
|
|
16
|
+ 'hovertext' => { 'css' => "#comic img", 'value' => "@title" }
|
17
|
17
|
}
|
18
|
18
|
}
|
19
|
|
- @checker = Agents::WebsiteAgent.new(:name => "xkcd", :options => @site, :keep_events_for => 2)
|
|
19
|
+ @checker = Agents::WebsiteAgent.new(:name => "xkcd", :options => @valid_options, :keep_events_for => 2)
|
20
|
20
|
@checker.user = users(:bob)
|
21
|
21
|
@checker.save!
|
22
|
22
|
end
|
23
|
23
|
|
|
24
|
+ it_behaves_like WebRequestConcern
|
|
25
|
+
|
24
|
26
|
describe "validations" do
|
25
|
27
|
before do
|
26
|
28
|
@checker.should be_valid
|
|
|
@@ -42,20 +44,6 @@ describe Agents::WebsiteAgent do
|
42
|
44
|
@checker.should be_valid
|
43
|
45
|
end
|
44
|
46
|
|
45
|
|
- it "should validate headers" do
|
46
|
|
- @checker.options['headers'] = "blah"
|
47
|
|
- @checker.should_not be_valid
|
48
|
|
-
|
49
|
|
- @checker.options['headers'] = ""
|
50
|
|
- @checker.should be_valid
|
51
|
|
-
|
52
|
|
- @checker.options['headers'] = {}
|
53
|
|
- @checker.should be_valid
|
54
|
|
-
|
55
|
|
- @checker.options['headers'] = { 'foo' => 'bar' }
|
56
|
|
- @checker.should be_valid
|
57
|
|
- end
|
58
|
|
-
|
59
|
47
|
it "should validate mode" do
|
60
|
48
|
@checker.options['mode'] = "nonsense"
|
61
|
49
|
@checker.should_not be_valid
|
|
|
@@ -97,16 +85,16 @@ describe Agents::WebsiteAgent do
|
97
|
85
|
|
98
|
86
|
it "should always save events when in :all mode" do
|
99
|
87
|
lambda {
|
100
|
|
- @site['mode'] = 'all'
|
101
|
|
- @checker.options = @site
|
|
88
|
+ @valid_options['mode'] = 'all'
|
|
89
|
+ @checker.options = @valid_options
|
102
|
90
|
@checker.check
|
103
|
91
|
@checker.check
|
104
|
92
|
}.should change { Event.count }.by(2)
|
105
|
93
|
end
|
106
|
94
|
|
107
|
95
|
it "should take uniqueness_look_back into account during deduplication" do
|
108
|
|
- @site['mode'] = 'all'
|
109
|
|
- @checker.options = @site
|
|
96
|
+ @valid_options['mode'] = 'all'
|
|
97
|
+ @checker.options = @valid_options
|
110
|
98
|
@checker.check
|
111
|
99
|
@checker.check
|
112
|
100
|
event = Event.last
|
|
|
@@ -114,47 +102,47 @@ describe Agents::WebsiteAgent do
|
114
|
102
|
event.save
|
115
|
103
|
|
116
|
104
|
lambda {
|
117
|
|
- @site['mode'] = 'on_change'
|
118
|
|
- @site['uniqueness_look_back'] = 2
|
119
|
|
- @checker.options = @site
|
|
105
|
+ @valid_options['mode'] = 'on_change'
|
|
106
|
+ @valid_options['uniqueness_look_back'] = 2
|
|
107
|
+ @checker.options = @valid_options
|
120
|
108
|
@checker.check
|
121
|
109
|
}.should_not change { Event.count }
|
122
|
110
|
|
123
|
111
|
lambda {
|
124
|
|
- @site['mode'] = 'on_change'
|
125
|
|
- @site['uniqueness_look_back'] = 1
|
126
|
|
- @checker.options = @site
|
|
112
|
+ @valid_options['mode'] = 'on_change'
|
|
113
|
+ @valid_options['uniqueness_look_back'] = 1
|
|
114
|
+ @checker.options = @valid_options
|
127
|
115
|
@checker.check
|
128
|
116
|
}.should change { Event.count }.by(1)
|
129
|
117
|
end
|
130
|
118
|
|
131
|
119
|
it "should log an error if the number of results for a set of extraction patterns differs" do
|
132
|
|
- @site['extract']['url']['css'] = "div"
|
133
|
|
- @checker.options = @site
|
|
120
|
+ @valid_options['extract']['url']['css'] = "div"
|
|
121
|
+ @checker.options = @valid_options
|
134
|
122
|
@checker.check
|
135
|
123
|
@checker.logs.first.message.should =~ /Got an uneven number of matches/
|
136
|
124
|
end
|
137
|
125
|
|
138
|
126
|
it "should accept an array for url" do
|
139
|
|
- @site['url'] = ["http://xkcd.com/1/", "http://xkcd.com/2/"]
|
140
|
|
- @checker.options = @site
|
|
127
|
+ @valid_options['url'] = ["http://xkcd.com/1/", "http://xkcd.com/2/"]
|
|
128
|
+ @checker.options = @valid_options
|
141
|
129
|
lambda { @checker.save! }.should_not raise_error;
|
142
|
130
|
lambda { @checker.check }.should_not raise_error;
|
143
|
131
|
end
|
144
|
132
|
|
145
|
133
|
it "should parse events from all urls in array" do
|
146
|
134
|
lambda {
|
147
|
|
- @site['url'] = ["http://xkcd.com/", "http://xkcd.com/"]
|
148
|
|
- @site['mode'] = 'all'
|
149
|
|
- @checker.options = @site
|
|
135
|
+ @valid_options['url'] = ["http://xkcd.com/", "http://xkcd.com/"]
|
|
136
|
+ @valid_options['mode'] = 'all'
|
|
137
|
+ @checker.options = @valid_options
|
150
|
138
|
@checker.check
|
151
|
139
|
}.should change { Event.count }.by(2)
|
152
|
140
|
end
|
153
|
141
|
|
154
|
142
|
it "should follow unique rules when parsing array of urls" do
|
155
|
143
|
lambda {
|
156
|
|
- @site['url'] = ["http://xkcd.com/", "http://xkcd.com/"]
|
157
|
|
- @checker.options = @site
|
|
144
|
+ @valid_options['url'] = ["http://xkcd.com/", "http://xkcd.com/"]
|
|
145
|
+ @checker.options = @valid_options
|
158
|
146
|
@checker.check
|
159
|
147
|
}.should change { Event.count }.by(1)
|
160
|
148
|
end
|
|
|
@@ -170,7 +158,7 @@ describe Agents::WebsiteAgent do
|
170
|
158
|
}, :status => 200)
|
171
|
159
|
site = {
|
172
|
160
|
'name' => "Some JSON Response",
|
173
|
|
- 'expected_update_period_in_days' => 2,
|
|
161
|
+ 'expected_update_period_in_days' => "2",
|
174
|
162
|
'type' => "json",
|
175
|
163
|
'url' => "http://no-encoding.example.com",
|
176
|
164
|
'mode' => 'on_change',
|
|
|
@@ -197,7 +185,7 @@ describe Agents::WebsiteAgent do
|
197
|
185
|
}, :status => 200)
|
198
|
186
|
site = {
|
199
|
187
|
'name' => "Some JSON Response",
|
200
|
|
- 'expected_update_period_in_days' => 2,
|
|
188
|
+ 'expected_update_period_in_days' => "2",
|
201
|
189
|
'type' => "json",
|
202
|
190
|
'url' => "http://wrong-encoding.example.com",
|
203
|
191
|
'mode' => 'on_change',
|
|
|
@@ -248,11 +236,11 @@ describe Agents::WebsiteAgent do
|
248
|
236
|
end
|
249
|
237
|
|
250
|
238
|
it "parses XPath" do
|
251
|
|
- @site['extract'].each { |key, value|
|
|
239
|
+ @valid_options['extract'].each { |key, value|
|
252
|
240
|
value.delete('css')
|
253
|
241
|
value['xpath'] = "//*[@id='comic']//img"
|
254
|
242
|
}
|
255
|
|
- @checker.options = @site
|
|
243
|
+ @checker.options = @valid_options
|
256
|
244
|
@checker.check
|
257
|
245
|
event = Event.last
|
258
|
246
|
event.payload['url'].should == "http://imgs.xkcd.com/comics/evolving.png"
|
|
|
@@ -263,13 +251,12 @@ describe Agents::WebsiteAgent do
|
263
|
251
|
it "should turn relative urls to absolute" do
|
264
|
252
|
rel_site = {
|
265
|
253
|
'name' => "XKCD",
|
266
|
|
- 'expected_update_period_in_days' => 2,
|
|
254
|
+ 'expected_update_period_in_days' => "2",
|
267
|
255
|
'type' => "html",
|
268
|
256
|
'url' => "http://xkcd.com",
|
269
|
257
|
'mode' => "on_change",
|
270
|
258
|
'extract' => {
|
271
|
|
- 'url' => {'css' => "#topLeft a", 'attr' => "href"},
|
272
|
|
- 'title' => {'css' => "#topLeft a", 'text' => "true"}
|
|
259
|
+ 'url' => {'css' => "#topLeft a", 'value' => "@href"},
|
273
|
260
|
}
|
274
|
261
|
}
|
275
|
262
|
rel = Agents::WebsiteAgent.new(:name => "xkcd", :options => rel_site)
|
|
|
@@ -280,6 +267,44 @@ describe Agents::WebsiteAgent do
|
280
|
267
|
event.payload['url'].should == "http://xkcd.com/about"
|
281
|
268
|
end
|
282
|
269
|
|
|
270
|
+ it "should return an integer value if XPath evaluates to one" do
|
|
271
|
+ rel_site = {
|
|
272
|
+ 'name' => "XKCD",
|
|
273
|
+ 'expected_update_period_in_days' => 2,
|
|
274
|
+ 'type' => "html",
|
|
275
|
+ 'url' => "http://xkcd.com",
|
|
276
|
+ 'mode' => "on_change",
|
|
277
|
+ 'extract' => {
|
|
278
|
+ 'num_links' => {'css' => "#comicLinks", 'value' => "count(./a)"}
|
|
279
|
+ }
|
|
280
|
+ }
|
|
281
|
+ rel = Agents::WebsiteAgent.new(:name => "xkcd", :options => rel_site)
|
|
282
|
+ rel.user = users(:bob)
|
|
283
|
+ rel.save!
|
|
284
|
+ rel.check
|
|
285
|
+ event = Event.last
|
|
286
|
+ event.payload['num_links'].should == "9"
|
|
287
|
+ end
|
|
288
|
+
|
|
289
|
+ it "should return all texts concatenated if XPath returns many text nodes" do
|
|
290
|
+ rel_site = {
|
|
291
|
+ 'name' => "XKCD",
|
|
292
|
+ 'expected_update_period_in_days' => 2,
|
|
293
|
+ 'type' => "html",
|
|
294
|
+ 'url' => "http://xkcd.com",
|
|
295
|
+ 'mode' => "on_change",
|
|
296
|
+ 'extract' => {
|
|
297
|
+ 'slogan' => {'css' => "#slogan", 'value' => ".//text()"}
|
|
298
|
+ }
|
|
299
|
+ }
|
|
300
|
+ rel = Agents::WebsiteAgent.new(:name => "xkcd", :options => rel_site)
|
|
301
|
+ rel.user = users(:bob)
|
|
302
|
+ rel.save!
|
|
303
|
+ rel.check
|
|
304
|
+ event = Event.last
|
|
305
|
+ event.payload['slogan'].should == "A webcomic of romance, sarcasm, math, and language."
|
|
306
|
+ end
|
|
307
|
+
|
283
|
308
|
describe "JSON" do
|
284
|
309
|
it "works with paths" do
|
285
|
310
|
json = {
|
|
|
@@ -291,7 +316,7 @@ describe Agents::WebsiteAgent do
|
291
|
316
|
stub_request(:any, /json-site/).to_return(:body => json.to_json, :status => 200)
|
292
|
317
|
site = {
|
293
|
318
|
'name' => "Some JSON Response",
|
294
|
|
- 'expected_update_period_in_days' => 2,
|
|
319
|
+ 'expected_update_period_in_days' => "2",
|
295
|
320
|
'type' => "json",
|
296
|
321
|
'url' => "http://json-site.com",
|
297
|
322
|
'mode' => 'on_change',
|
|
|
@@ -322,7 +347,7 @@ describe Agents::WebsiteAgent do
|
322
|
347
|
stub_request(:any, /json-site/).to_return(:body => json.to_json, :status => 200)
|
323
|
348
|
site = {
|
324
|
349
|
'name' => "Some JSON Response",
|
325
|
|
- 'expected_update_period_in_days' => 2,
|
|
350
|
+ 'expected_update_period_in_days' => "2",
|
326
|
351
|
'type' => "json",
|
327
|
352
|
'url' => "http://json-site.com",
|
328
|
353
|
'mode' => 'on_change',
|
|
|
@@ -358,7 +383,7 @@ describe Agents::WebsiteAgent do
|
358
|
383
|
stub_request(:any, /json-site/).to_return(:body => json.to_json, :status => 200)
|
359
|
384
|
site = {
|
360
|
385
|
'name' => "Some JSON Response",
|
361
|
|
- 'expected_update_period_in_days' => 2,
|
|
386
|
+ 'expected_update_period_in_days' => "2",
|
362
|
387
|
'type' => "json",
|
363
|
388
|
'url' => "http://json-site.com",
|
364
|
389
|
'mode' => 'on_change'
|
|
|
@@ -382,7 +407,7 @@ describe Agents::WebsiteAgent do
|
382
|
407
|
@event.payload = { 'url' => "http://xkcd.com" }
|
383
|
408
|
|
384
|
409
|
lambda {
|
385
|
|
- @checker.options = @site
|
|
410
|
+ @checker.options = @valid_options
|
386
|
411
|
@checker.receive([@event])
|
387
|
412
|
}.should change { Event.count }.by(1)
|
388
|
413
|
end
|
|
|
@@ -394,20 +419,20 @@ describe Agents::WebsiteAgent do
|
394
|
419
|
stub_request(:any, /example/).
|
395
|
420
|
with(headers: { 'Authorization' => "Basic #{['user:pass'].pack('m').chomp}" }).
|
396
|
421
|
to_return(:body => File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), :status => 200)
|
397
|
|
- @site = {
|
|
422
|
+ @valid_options = {
|
398
|
423
|
'name' => "XKCD",
|
399
|
|
- 'expected_update_period_in_days' => 2,
|
|
424
|
+ 'expected_update_period_in_days' => "2",
|
400
|
425
|
'type' => "html",
|
401
|
426
|
'url' => "http://www.example.com",
|
402
|
427
|
'mode' => 'on_change',
|
403
|
428
|
'extract' => {
|
404
|
|
- 'url' => { 'css' => "#comic img", 'attr' => "src" },
|
405
|
|
- 'title' => { 'css' => "#comic img", 'attr' => "alt" },
|
406
|
|
- 'hovertext' => { 'css' => "#comic img", 'attr' => "title" }
|
|
429
|
+ 'url' => { 'css' => "#comic img", 'value' => "@src" },
|
|
430
|
+ 'title' => { 'css' => "#comic img", 'value' => "@alt" },
|
|
431
|
+ 'hovertext' => { 'css' => "#comic img", 'value' => "@title" }
|
407
|
432
|
},
|
408
|
433
|
'basic_auth' => "user:pass"
|
409
|
434
|
}
|
410
|
|
- @checker = Agents::WebsiteAgent.new(:name => "auth", :options => @site)
|
|
435
|
+ @checker = Agents::WebsiteAgent.new(:name => "auth", :options => @valid_options)
|
411
|
436
|
@checker.user = users(:bob)
|
412
|
437
|
@checker.save!
|
413
|
438
|
end
|
|
|
@@ -425,18 +450,18 @@ describe Agents::WebsiteAgent do
|
425
|
450
|
stub_request(:any, /example/).
|
426
|
451
|
with(headers: { 'foo' => 'bar', 'user_agent' => /Faraday/ }).
|
427
|
452
|
to_return(:body => File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), :status => 200)
|
428
|
|
- @site = {
|
|
453
|
+ @valid_options = {
|
429
|
454
|
'name' => "XKCD",
|
430
|
|
- 'expected_update_period_in_days' => 2,
|
|
455
|
+ 'expected_update_period_in_days' => "2",
|
431
|
456
|
'type' => "html",
|
432
|
457
|
'url' => "http://www.example.com",
|
433
|
458
|
'mode' => 'on_change',
|
434
|
459
|
'headers' => { 'foo' => 'bar' },
|
435
|
460
|
'extract' => {
|
436
|
|
- 'url' => { 'css' => "#comic img", 'attr' => "src" },
|
|
461
|
+ 'url' => { 'css' => "#comic img", 'value' => "@src" },
|
437
|
462
|
}
|
438
|
463
|
}
|
439
|
|
- @checker = Agents::WebsiteAgent.new(:name => "ua", :options => @site)
|
|
464
|
+ @checker = Agents::WebsiteAgent.new(:name => "ua", :options => @valid_options)
|
440
|
465
|
@checker.user = users(:bob)
|
441
|
466
|
@checker.save!
|
442
|
467
|
end
|
|
|
@@ -76,3 +76,39 @@ describe Event do
|
76
|
76
|
end
|
77
|
77
|
end
|
78
|
78
|
end
|
|
79
|
+
|
|
80
|
+describe EventDrop do
|
|
81
|
+ def interpolate(string, event)
|
|
82
|
+ event.agent.interpolate_string(string, event.to_liquid)
|
|
83
|
+ end
|
|
84
|
+
|
|
85
|
+ before do
|
|
86
|
+ @event = Event.new
|
|
87
|
+ @event.agent = agents(:jane_weather_agent)
|
|
88
|
+ @event.payload = {
|
|
89
|
+ 'title' => 'some title',
|
|
90
|
+ 'url' => 'http://some.site.example.org/',
|
|
91
|
+ }
|
|
92
|
+ @event.save!
|
|
93
|
+ end
|
|
94
|
+
|
|
95
|
+ it 'should be created via Agent#to_liquid' do
|
|
96
|
+ @event.to_liquid.class.should be(EventDrop)
|
|
97
|
+ end
|
|
98
|
+
|
|
99
|
+ it 'should have attributes of its payload' do
|
|
100
|
+ t = '{{title}}: {{url}}'
|
|
101
|
+ interpolate(t, @event).should eq('some title: http://some.site.example.org/')
|
|
102
|
+ end
|
|
103
|
+
|
|
104
|
+ it 'should be iteratable' do
|
|
105
|
+ # to_liquid returns self
|
|
106
|
+ t = "{% for pair in to_liquid %}{{pair | join:':' }}\n{% endfor %}"
|
|
107
|
+ interpolate(t, @event).should eq("title:some title\nurl:http://some.site.example.org/\n")
|
|
108
|
+ end
|
|
109
|
+
|
|
110
|
+ it 'should have agent' do
|
|
111
|
+ t = '{{agent.name}}'
|
|
112
|
+ interpolate(t, @event).should eq('SF Weather')
|
|
113
|
+ end
|
|
114
|
+end
|
|
|
@@ -0,0 +1,88 @@
|
|
1
|
+require 'spec_helper'
|
|
2
|
+
|
|
3
|
+shared_examples_for EmailConcern do
|
|
4
|
+ let(:valid_options) {
|
|
5
|
+ {
|
|
6
|
+ :subject => "hello!",
|
|
7
|
+ :expected_receive_period_in_days => "2"
|
|
8
|
+ }
|
|
9
|
+ }
|
|
10
|
+
|
|
11
|
+ let(:agent) do
|
|
12
|
+ _agent = described_class.new(:name => "some email agent", :options => valid_options)
|
|
13
|
+ _agent.user = users(:jane)
|
|
14
|
+ _agent
|
|
15
|
+ end
|
|
16
|
+
|
|
17
|
+ describe "validations" do
|
|
18
|
+ it "should be valid" do
|
|
19
|
+ agent.should be_valid
|
|
20
|
+ end
|
|
21
|
+
|
|
22
|
+ it "should validate the presence of 'subject'" do
|
|
23
|
+ agent.options['subject'] = ''
|
|
24
|
+ agent.should_not be_valid
|
|
25
|
+
|
|
26
|
+ agent.options['subject'] = nil
|
|
27
|
+ agent.should_not be_valid
|
|
28
|
+ end
|
|
29
|
+
|
|
30
|
+ it "should validate the presence of 'expected_receive_period_in_days'" do
|
|
31
|
+ agent.options['expected_receive_period_in_days'] = ''
|
|
32
|
+ agent.should_not be_valid
|
|
33
|
+
|
|
34
|
+ agent.options['expected_receive_period_in_days'] = nil
|
|
35
|
+ agent.should_not be_valid
|
|
36
|
+ end
|
|
37
|
+
|
|
38
|
+ it "should validate that recipients, when provided, is one or more valid email addresses" do
|
|
39
|
+ agent.options['recipients'] = ''
|
|
40
|
+ agent.should be_valid
|
|
41
|
+
|
|
42
|
+ agent.options['recipients'] = nil
|
|
43
|
+ agent.should be_valid
|
|
44
|
+
|
|
45
|
+ agent.options['recipients'] = 'bob@example.com'
|
|
46
|
+ agent.should be_valid
|
|
47
|
+
|
|
48
|
+ agent.options['recipients'] = ['bob@example.com']
|
|
49
|
+ agent.should be_valid
|
|
50
|
+
|
|
51
|
+ agent.options['recipients'] = ['bob@example.com', 'jane@example.com']
|
|
52
|
+ agent.should be_valid
|
|
53
|
+
|
|
54
|
+ agent.options['recipients'] = ['bob@example.com', 'example.com']
|
|
55
|
+ agent.should_not be_valid
|
|
56
|
+
|
|
57
|
+ agent.options['recipients'] = ['hi!']
|
|
58
|
+ agent.should_not be_valid
|
|
59
|
+
|
|
60
|
+ agent.options['recipients'] = { :foo => "bar" }
|
|
61
|
+ agent.should_not be_valid
|
|
62
|
+
|
|
63
|
+ agent.options['recipients'] = "wut"
|
|
64
|
+ agent.should_not be_valid
|
|
65
|
+ end
|
|
66
|
+ end
|
|
67
|
+
|
|
68
|
+ describe "#recipients" do
|
|
69
|
+ it "defaults to the user's email address" do
|
|
70
|
+ agent.recipients.should == [users(:jane).email]
|
|
71
|
+ end
|
|
72
|
+
|
|
73
|
+ it "wraps a string with an array" do
|
|
74
|
+ agent.options['recipients'] = 'bob@bob.com'
|
|
75
|
+ agent.recipients.should == ['bob@bob.com']
|
|
76
|
+ end
|
|
77
|
+
|
|
78
|
+ it "handles an array" do
|
|
79
|
+ agent.options['recipients'] = ['bob@bob.com', 'jane@jane.com']
|
|
80
|
+ agent.recipients.should == ['bob@bob.com', 'jane@jane.com']
|
|
81
|
+ end
|
|
82
|
+
|
|
83
|
+ it "interpolates" do
|
|
84
|
+ agent.options['recipients'] = "{{ username }}@{{ domain }}"
|
|
85
|
+ agent.recipients('username' => 'bob', 'domain' => 'example.com').should == ["bob@example.com"]
|
|
86
|
+ end
|
|
87
|
+ end
|
|
88
|
+end
|
|
|
@@ -20,7 +20,7 @@ shared_examples_for LiquidInterpolatable do
|
20
|
20
|
|
21
|
21
|
describe "interpolating liquid templates" do
|
22
|
22
|
it "should work" do
|
23
|
|
- @checker.interpolate_options(@checker.options, @event.payload).should == {
|
|
23
|
+ @checker.interpolate_options(@checker.options, @event).should == {
|
24
|
24
|
"normal" => "just some normal text",
|
25
|
25
|
"variable" => "hello",
|
26
|
26
|
"text" => "Some test with an embedded hello",
|
|
|
@@ -30,7 +30,7 @@ shared_examples_for LiquidInterpolatable do
|
30
|
30
|
|
31
|
31
|
it "should work with arrays", focus: true do
|
32
|
32
|
@checker.options = {"value" => ["{{variable}}", "Much array", "Hey, {{hello_world}}"]}
|
33
|
|
- @checker.interpolate_options(@checker.options, @event.payload).should == {
|
|
33
|
+ @checker.interpolate_options(@checker.options, @event).should == {
|
34
|
34
|
"value" => ["hello", "Much array", "Hey, Hello world"]
|
35
|
35
|
}
|
36
|
36
|
end
|
|
|
@@ -38,7 +38,7 @@ shared_examples_for LiquidInterpolatable do
|
38
|
38
|
it "should work recursively" do
|
39
|
39
|
@checker.options['hash'] = {'recursive' => "{{variable}}"}
|
40
|
40
|
@checker.options['indifferent_hash'] = ActiveSupport::HashWithIndifferentAccess.new({'recursive' => "{{variable}}"})
|
41
|
|
- @checker.interpolate_options(@checker.options, @event.payload).should == {
|
|
41
|
+ @checker.interpolate_options(@checker.options, @event).should == {
|
42
|
42
|
"normal" => "just some normal text",
|
43
|
43
|
"variable" => "hello",
|
44
|
44
|
"text" => "Some test with an embedded hello",
|
|
|
@@ -49,8 +49,8 @@ shared_examples_for LiquidInterpolatable do
|
49
|
49
|
end
|
50
|
50
|
|
51
|
51
|
it "should work for strings" do
|
52
|
|
- @checker.interpolate_string("{{variable}}", @event.payload).should == "hello"
|
53
|
|
- @checker.interpolate_string("{{variable}} you", @event.payload).should == "hello you"
|
|
52
|
+ @checker.interpolate_string("{{variable}}", @event).should == "hello"
|
|
53
|
+ @checker.interpolate_string("{{variable}} you", @event).should == "hello you"
|
54
|
54
|
end
|
55
|
55
|
end
|
56
|
56
|
|
|
|
@@ -0,0 +1,66 @@
|
|
1
|
+require 'spec_helper'
|
|
2
|
+
|
|
3
|
+shared_examples_for WebRequestConcern do
|
|
4
|
+ let(:agent) do
|
|
5
|
+ _agent = described_class.new(:name => "some agent", :options => @valid_options || {})
|
|
6
|
+ _agent.user = users(:jane)
|
|
7
|
+ _agent
|
|
8
|
+ end
|
|
9
|
+
|
|
10
|
+ describe "validations" do
|
|
11
|
+ it "should be valid" do
|
|
12
|
+ agent.should be_valid
|
|
13
|
+ end
|
|
14
|
+
|
|
15
|
+ it "should validate user_agent" do
|
|
16
|
+ agent.options['user_agent'] = nil
|
|
17
|
+ agent.should be_valid
|
|
18
|
+
|
|
19
|
+ agent.options['user_agent'] = ""
|
|
20
|
+ agent.should be_valid
|
|
21
|
+
|
|
22
|
+ agent.options['user_agent'] = "foo"
|
|
23
|
+ agent.should be_valid
|
|
24
|
+
|
|
25
|
+ agent.options['user_agent'] = ["foo"]
|
|
26
|
+ agent.should_not be_valid
|
|
27
|
+
|
|
28
|
+ agent.options['user_agent'] = 1
|
|
29
|
+ agent.should_not be_valid
|
|
30
|
+ end
|
|
31
|
+
|
|
32
|
+ it "should validate headers" do
|
|
33
|
+ agent.options['headers'] = "blah"
|
|
34
|
+ agent.should_not be_valid
|
|
35
|
+
|
|
36
|
+ agent.options['headers'] = ""
|
|
37
|
+ agent.should be_valid
|
|
38
|
+
|
|
39
|
+ agent.options['headers'] = {}
|
|
40
|
+ agent.should be_valid
|
|
41
|
+
|
|
42
|
+ agent.options['headers'] = { 'foo' => 'bar' }
|
|
43
|
+ agent.should be_valid
|
|
44
|
+ end
|
|
45
|
+
|
|
46
|
+ it "should validate basic_auth" do
|
|
47
|
+ agent.options['basic_auth'] = "foo:bar"
|
|
48
|
+ agent.should be_valid
|
|
49
|
+
|
|
50
|
+ agent.options['basic_auth'] = ["foo", "bar"]
|
|
51
|
+ agent.should be_valid
|
|
52
|
+
|
|
53
|
+ agent.options['basic_auth'] = ""
|
|
54
|
+ agent.should be_valid
|
|
55
|
+
|
|
56
|
+ agent.options['basic_auth'] = nil
|
|
57
|
+ agent.should be_valid
|
|
58
|
+
|
|
59
|
+ agent.options['basic_auth'] = "blah"
|
|
60
|
+ agent.should_not be_valid
|
|
61
|
+
|
|
62
|
+ agent.options['basic_auth'] = ["blah"]
|
|
63
|
+ agent.should_not be_valid
|
|
64
|
+ end
|
|
65
|
+ end
|
|
66
|
+end
|
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+require 'vcr'
|
|
2
|
+
|
|
3
|
+VCR.configure do |c|
|
|
4
|
+ c.cassette_library_dir = 'spec/cassettes'
|
|
5
|
+ c.allow_http_connections_when_no_cassette = true
|
|
6
|
+ c.hook_into :webmock
|
|
7
|
+ c.default_cassette_options = { record: :new_episodes}
|
|
8
|
+ c.configure_rspec_metadata!
|
|
9
|
+end
|